| /* | |
| * Copyright (C) 2015 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #define LOG_TAG "ZIPARCHIVE" | |
| // Read-only stream access to Zip Archive entries. | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <string.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <memory> | |
| #include <vector> | |
| #include <android-base/file.h> | |
| #include <log/log.h> | |
| #include <ziparchive/zip_archive.h> | |
| #include <ziparchive/zip_archive_stream_entry.h> | |
| #include <zlib.h> | |
| #include "zip_archive_private.h" | |
| static constexpr size_t kBufSize = 65535; | |
| bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) { | |
| crc32_ = entry.crc32; | |
| offset_ = entry.offset; | |
| return true; | |
| } | |
| class ZipArchiveStreamEntryUncompressed : public ZipArchiveStreamEntry { | |
| public: | |
| explicit ZipArchiveStreamEntryUncompressed(ZipArchiveHandle handle) | |
| : ZipArchiveStreamEntry(handle) {} | |
| virtual ~ZipArchiveStreamEntryUncompressed() {} | |
| const std::vector<uint8_t>* Read() override; | |
| bool Verify() override; | |
| protected: | |
| bool Init(const ZipEntry& entry) override; | |
| uint32_t length_ = 0u; | |
| private: | |
| std::vector<uint8_t> data_; | |
| uint32_t computed_crc32_ = 0u; | |
| }; | |
| bool ZipArchiveStreamEntryUncompressed::Init(const ZipEntry& entry) { | |
| if (!ZipArchiveStreamEntry::Init(entry)) { | |
| return false; | |
| } | |
| length_ = entry.uncompressed_length; | |
| data_.resize(kBufSize); | |
| computed_crc32_ = 0; | |
| return true; | |
| } | |
| const std::vector<uint8_t>* ZipArchiveStreamEntryUncompressed::Read() { | |
| if (length_ == 0) { | |
| return nullptr; | |
| } | |
| size_t bytes = (length_ > data_.size()) ? data_.size() : length_; | |
| ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_); | |
| errno = 0; | |
| if (!archive->mapped_zip.ReadAtOffset(data_.data(), bytes, offset_)) { | |
| if (errno != 0) { | |
| ALOGE("Error reading from archive fd: %s", strerror(errno)); | |
| } else { | |
| ALOGE("Short read of zip file, possibly corrupted zip?"); | |
| } | |
| length_ = 0; | |
| return nullptr; | |
| } | |
| if (bytes < data_.size()) { | |
| data_.resize(bytes); | |
| } | |
| computed_crc32_ = crc32(computed_crc32_, data_.data(), data_.size()); | |
| length_ -= bytes; | |
| offset_ += bytes; | |
| return &data_; | |
| } | |
| bool ZipArchiveStreamEntryUncompressed::Verify() { | |
| return length_ == 0 && crc32_ == computed_crc32_; | |
| } | |
| class ZipArchiveStreamEntryCompressed : public ZipArchiveStreamEntry { | |
| public: | |
| explicit ZipArchiveStreamEntryCompressed(ZipArchiveHandle handle) | |
| : ZipArchiveStreamEntry(handle) {} | |
| virtual ~ZipArchiveStreamEntryCompressed(); | |
| const std::vector<uint8_t>* Read() override; | |
| bool Verify() override; | |
| protected: | |
| bool Init(const ZipEntry& entry) override; | |
| private: | |
| bool z_stream_init_ = false; | |
| z_stream z_stream_; | |
| std::vector<uint8_t> in_; | |
| std::vector<uint8_t> out_; | |
| uint32_t uncompressed_length_ = 0u; | |
| uint32_t compressed_length_ = 0u; | |
| uint32_t computed_crc32_ = 0u; | |
| }; | |
| // This method is using libz macros with old-style-casts | |
| #pragma GCC diagnostic push | |
| #pragma GCC diagnostic ignored "-Wold-style-cast" | |
| static inline int zlib_inflateInit2(z_stream* stream, int window_bits) { | |
| return inflateInit2(stream, window_bits); | |
| } | |
| #pragma GCC diagnostic pop | |
| bool ZipArchiveStreamEntryCompressed::Init(const ZipEntry& entry) { | |
| if (!ZipArchiveStreamEntry::Init(entry)) { | |
| return false; | |
| } | |
| // Initialize the zlib stream struct. | |
| memset(&z_stream_, 0, sizeof(z_stream_)); | |
| z_stream_.zalloc = Z_NULL; | |
| z_stream_.zfree = Z_NULL; | |
| z_stream_.opaque = Z_NULL; | |
| z_stream_.next_in = nullptr; | |
| z_stream_.avail_in = 0; | |
| z_stream_.avail_out = 0; | |
| z_stream_.data_type = Z_UNKNOWN; | |
| // Use the undocumented "negative window bits" feature to tell zlib | |
| // that there's no zlib header waiting for it. | |
| int zerr = zlib_inflateInit2(&z_stream_, -MAX_WBITS); | |
| if (zerr != Z_OK) { | |
| if (zerr == Z_VERSION_ERROR) { | |
| ALOGE("Installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); | |
| } else { | |
| ALOGE("Call to inflateInit2 failed (zerr=%d)", zerr); | |
| } | |
| return false; | |
| } | |
| z_stream_init_ = true; | |
| uncompressed_length_ = entry.uncompressed_length; | |
| compressed_length_ = entry.compressed_length; | |
| out_.resize(kBufSize); | |
| in_.resize(kBufSize); | |
| computed_crc32_ = 0; | |
| return true; | |
| } | |
| ZipArchiveStreamEntryCompressed::~ZipArchiveStreamEntryCompressed() { | |
| if (z_stream_init_) { | |
| inflateEnd(&z_stream_); | |
| z_stream_init_ = false; | |
| } | |
| } | |
| bool ZipArchiveStreamEntryCompressed::Verify() { | |
| return z_stream_init_ && uncompressed_length_ == 0 && compressed_length_ == 0 && | |
| crc32_ == computed_crc32_; | |
| } | |
| const std::vector<uint8_t>* ZipArchiveStreamEntryCompressed::Read() { | |
| if (z_stream_.avail_out == 0) { | |
| z_stream_.next_out = out_.data(); | |
| z_stream_.avail_out = out_.size(); | |
| ; | |
| } | |
| while (true) { | |
| if (z_stream_.avail_in == 0) { | |
| if (compressed_length_ == 0) { | |
| return nullptr; | |
| } | |
| size_t bytes = (compressed_length_ > in_.size()) ? in_.size() : compressed_length_; | |
| ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_); | |
| errno = 0; | |
| if (!archive->mapped_zip.ReadAtOffset(in_.data(), bytes, offset_)) { | |
| if (errno != 0) { | |
| ALOGE("Error reading from archive fd: %s", strerror(errno)); | |
| } else { | |
| ALOGE("Short read of zip file, possibly corrupted zip?"); | |
| } | |
| return nullptr; | |
| } | |
| compressed_length_ -= bytes; | |
| offset_ += bytes; | |
| z_stream_.next_in = in_.data(); | |
| z_stream_.avail_in = bytes; | |
| } | |
| int zerr = inflate(&z_stream_, Z_NO_FLUSH); | |
| if (zerr != Z_OK && zerr != Z_STREAM_END) { | |
| ALOGE("inflate zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)", zerr, z_stream_.next_in, | |
| z_stream_.avail_in, z_stream_.next_out, z_stream_.avail_out); | |
| return nullptr; | |
| } | |
| if (z_stream_.avail_out == 0) { | |
| uncompressed_length_ -= out_.size(); | |
| computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size()); | |
| return &out_; | |
| } | |
| if (zerr == Z_STREAM_END) { | |
| if (z_stream_.avail_out != 0) { | |
| // Resize the vector down to the actual size of the data. | |
| out_.resize(out_.size() - z_stream_.avail_out); | |
| computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size()); | |
| uncompressed_length_ -= out_.size(); | |
| return &out_; | |
| } | |
| return nullptr; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| class ZipArchiveStreamEntryRawCompressed : public ZipArchiveStreamEntryUncompressed { | |
| public: | |
| explicit ZipArchiveStreamEntryRawCompressed(ZipArchiveHandle handle) | |
| : ZipArchiveStreamEntryUncompressed(handle) {} | |
| virtual ~ZipArchiveStreamEntryRawCompressed() {} | |
| bool Verify() override; | |
| protected: | |
| bool Init(const ZipEntry& entry) override; | |
| }; | |
| bool ZipArchiveStreamEntryRawCompressed::Init(const ZipEntry& entry) { | |
| if (!ZipArchiveStreamEntryUncompressed::Init(entry)) { | |
| return false; | |
| } | |
| length_ = entry.compressed_length; | |
| return true; | |
| } | |
| bool ZipArchiveStreamEntryRawCompressed::Verify() { | |
| return length_ == 0; | |
| } | |
| ZipArchiveStreamEntry* ZipArchiveStreamEntry::Create(ZipArchiveHandle handle, | |
| const ZipEntry& entry) { | |
| ZipArchiveStreamEntry* stream = nullptr; | |
| if (entry.method != kCompressStored) { | |
| stream = new ZipArchiveStreamEntryCompressed(handle); | |
| } else { | |
| stream = new ZipArchiveStreamEntryUncompressed(handle); | |
| } | |
| if (stream && !stream->Init(entry)) { | |
| delete stream; | |
| stream = nullptr; | |
| } | |
| return stream; | |
| } | |
| ZipArchiveStreamEntry* ZipArchiveStreamEntry::CreateRaw(ZipArchiveHandle handle, | |
| const ZipEntry& entry) { | |
| ZipArchiveStreamEntry* stream = nullptr; | |
| if (entry.method == kCompressStored) { | |
| // Not compressed, don't need to do anything special. | |
| stream = new ZipArchiveStreamEntryUncompressed(handle); | |
| } else { | |
| stream = new ZipArchiveStreamEntryRawCompressed(handle); | |
| } | |
| if (stream && !stream->Init(entry)) { | |
| delete stream; | |
| stream = nullptr; | |
| } | |
| return stream; | |
| } |