Add a ZipArchiveStreamEntry class.

This allows someone to stream the data out of a zip archive
instead of extracting to a file or to memory.

Included in this change is a small cleanup of the makefile.

Change-Id: I8b679a679c3502ff4ea0bc4f9e918303657fa424
diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc
new file mode 100644
index 0000000..f618835
--- /dev/null
+++ b/libziparchive/zip_archive_stream_entry.cc
@@ -0,0 +1,305 @@
+/*
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <vector>
+
+#define LOG_TAG "ZIPARCHIVE"
+#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) {
+  ZipArchive* archive = reinterpret_cast<ZipArchive*>(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:
+  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_;
+
+ private:
+  std::vector<uint8_t> 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<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 (!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:
+  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_;
+  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<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 (!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:
+  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;
+}