/* * 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. */ // Read-only stream access to Zip Archive entries. #include #include #include #include #include #include #include #define LOG_TAG "ZIPARCHIVE" #include #include #include #include #include #include "zip_archive_private.h" static constexpr size_t kBufSize = 65535; bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) { ZipArchive* archive = reinterpret_cast(handle_); off64_t data_offset = entry.offset; if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) { ALOGW("lseek to data at %" PRId64 " failed: %s", data_offset, strerror(errno)); return false; } crc32_ = entry.crc32; return true; } class ZipArchiveStreamEntryUncompressed : public ZipArchiveStreamEntry { public: explicit ZipArchiveStreamEntryUncompressed(ZipArchiveHandle handle) : ZipArchiveStreamEntry(handle) {} virtual ~ZipArchiveStreamEntryUncompressed() {} const std::vector* Read() override; bool Verify() override; protected: bool Init(const ZipEntry& entry) override; uint32_t length_; private: std::vector data_; uint32_t computed_crc32_; }; 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* ZipArchiveStreamEntryUncompressed::Read() { if (length_ == 0) { return nullptr; } size_t bytes = (length_ > data_.size()) ? data_.size() : length_; ZipArchive* archive = reinterpret_cast(handle_); errno = 0; if (!android::base::ReadFully(archive->fd, data_.data(), bytes)) { 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; 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* Read() override; bool Verify() override; protected: bool Init(const ZipEntry& entry) override; private: bool z_stream_init_ = false; z_stream z_stream_; std::vector in_; std::vector out_; uint32_t uncompressed_length_; uint32_t compressed_length_; uint32_t computed_crc32_; }; // 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* 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(handle_); errno = 0; if (!android::base::ReadFully(archive->fd, in_.data(), bytes)) { 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; 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; }