Commit 2919be01 authored by grt's avatar grt Committed by Commit bot

Add ZipReader::ExtractCurrentEntry with a delegate interface.

This change gives consumers of ZipReader a way to have the current entry
streamed to them via a Delegate interface. It also:

- Reduces duplication in the ExtractCurrentEntry* functions.
- Uses the heap rather than the stack for intermediate buffers.
- Changes ExtractCurrentEntryToFd to ExtractCurrentEntryToFile, making
  it cross-platform in the process.

BUG=462584

Review URL: https://codereview.chromium.org/1014653002

Cr-Commit-Position: refs/heads/master@{#320948}
parent 4a4b5584
This diff is collapsed.
......@@ -23,6 +23,21 @@
namespace zip {
// A delegate interface used to stream out an entry; see
// ZipReader::ExtractCurrentEntry.
class WriterDelegate {
public:
virtual ~WriterDelegate() {}
// Invoked once before any data is streamed out to pave the way (e.g., to open
// the output file). Return false on failure to cancel extraction.
virtual bool PrepareOutput() = 0;
// Invoked to write the next chunk of data. Return false on failure to cancel
// extraction.
virtual bool WriteBytes(const char* data, int num_bytes) = 0;
};
// This class is used for reading zip files. A typical use case of this
// class is to scan entries in a zip file and extract them. The code will
// look like:
......@@ -141,6 +156,9 @@ class ZipReader {
// success. On failure, current_entry_info() becomes NULL.
bool LocateAndOpenEntry(const base::FilePath& path_in_zip);
// Extracts the current entry in chunks to |delegate|.
bool ExtractCurrentEntry(WriterDelegate* delegate) const;
// Extracts the current entry to the given output file path. If the
// current file is a directory, just creates a directory
// instead. Returns true on success. OpenCurrentEntryInZip() must be
......@@ -148,7 +166,8 @@ class ZipReader {
//
// This function preserves the timestamp of the original entry. If that
// timestamp is not valid, the timestamp will be set to the current time.
bool ExtractCurrentEntryToFilePath(const base::FilePath& output_file_path);
bool ExtractCurrentEntryToFilePath(
const base::FilePath& output_file_path) const;
// Asynchronously extracts the current entry to the given output file path.
// If the current entry is a directory it just creates the directory
......@@ -174,13 +193,11 @@ class ZipReader {
// This function preserves the timestamp of the original entry. If that
// timestamp is not valid, the timestamp will be set to the current time.
bool ExtractCurrentEntryIntoDirectory(
const base::FilePath& output_directory_path);
const base::FilePath& output_directory_path) const;
#if defined(OS_POSIX)
// Extracts the current entry by writing directly to a file descriptor.
// Does not close the file descriptor. Returns true on success.
bool ExtractCurrentEntryToFd(int fd);
#endif
// Extracts the current entry by writing directly to a platform file.
// Does not close the file. Returns true on success.
bool ExtractCurrentEntryToFile(base::File* file) const;
// Extracts the current entry into memory. If the current entry is a directory
// the |output| parameter is set to the empty string. If the current entry is
......@@ -232,6 +249,30 @@ class ZipReader {
DISALLOW_COPY_AND_ASSIGN(ZipReader);
};
// A writer delegate that writes to a given File.
class FileWriterDelegate : public WriterDelegate {
public:
explicit FileWriterDelegate(base::File* file);
// Truncates the file to the number of bytes written.
~FileWriterDelegate() override;
// WriterDelegate methods:
// Seeks to the beginning of the file, returning false if the seek fails.
bool PrepareOutput() override;
// Writes |num_bytes| bytes of |data| to the file, returning false on error or
// if not all bytes could be written.
bool WriteBytes(const char* data, int num_bytes) override;
private:
base::File* file_;
int64_t file_length_;
DISALLOW_COPY_AND_ASSIGN(FileWriterDelegate);
};
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
......@@ -18,10 +18,14 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "third_party/zlib/google/zip_internal.h"
using ::testing::Return;
using ::testing::_;
namespace {
const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
......@@ -47,6 +51,8 @@ class FileWrapper {
base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
base::File* file() { return &file_; }
private:
base::File file_;
};
......@@ -93,6 +99,12 @@ class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> {
int64 current_progress_;
};
class MockWriterDelegate : public zip::WriterDelegate {
public:
MOCK_METHOD0(PrepareOutput, bool());
MOCK_METHOD2(WriteBytes, bool(const char*, int));
};
} // namespace
namespace zip {
......@@ -287,8 +299,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) {
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
}
#if defined(OS_POSIX)
TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFile_RegularFile) {
ZipReader reader;
FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
......@@ -296,18 +307,16 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
base::FilePath out_path = test_dir_.AppendASCII("quux.txt");
FileWrapper out_fd_w(out_path, FileWrapper::READ_WRITE);
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
ASSERT_TRUE(reader.ExtractCurrentEntryToFd(out_fd_w.platform_file()));
ASSERT_TRUE(reader.ExtractCurrentEntryToFile(out_fd_w.file()));
// Read the output file and compute the MD5.
std::string output;
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
&output));
ASSERT_TRUE(base::ReadFileToString(out_path, &output));
const std::string md5 = base::MD5String(output);
EXPECT_EQ(kQuuxExpectedMD5, md5);
// quux.txt should be larger than kZipBufSize so that we can exercise
// the loop in ExtractCurrentEntry().
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
}
#endif
TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) {
ZipReader reader;
......@@ -582,4 +591,97 @@ TEST_F(ZipReaderTest, DISABLED_LeakDetectionTest) {
}
}
// Test that when WriterDelegate::PrepareMock returns false, no other methods on
// the delegate are called and the extraction fails.
TEST_F(ZipReaderTest, ExtractCurrentEntryPrepareFailure) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput())
.WillOnce(Return(false));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
}
// Test that when WriterDelegate::WriteBytes returns false, no other methods on
// the delegate are called and the extraction fails.
TEST_F(ZipReaderTest, ExtractCurrentEntryWriteBytesFailure) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput())
.WillOnce(Return(true));
EXPECT_CALL(mock_writer, WriteBytes(_, _))
.WillOnce(Return(false));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
}
// Test that extraction succeeds when the writer delegate reports all is well.
TEST_F(ZipReaderTest, ExtractCurrentEntrySuccess) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput())
.WillOnce(Return(true));
EXPECT_CALL(mock_writer, WriteBytes(_, _))
.WillRepeatedly(Return(true));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
ASSERT_TRUE(reader.ExtractCurrentEntry(&mock_writer));
}
class FileWriterDelegateTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
file_.Initialize(temp_file_path_, (base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_READ |
base::File::FLAG_WRITE |
base::File::FLAG_TEMPORARY |
base::File::FLAG_DELETE_ON_CLOSE));
ASSERT_TRUE(file_.IsValid());
}
// Writes data to the file, leaving the current position at the end of the
// write.
void PopulateFile() {
static const char kSomeData[] = "this sure is some data.";
static const size_t kSomeDataLen = sizeof(kSomeData) - 1;
ASSERT_NE(-1LL, file_.Write(0LL, kSomeData, kSomeDataLen));
}
base::FilePath temp_file_path_;
base::File file_;
};
TEST_F(FileWriterDelegateTest, WriteToStartAndTruncate) {
// Write stuff and advance.
PopulateFile();
// This should rewind, write, then truncate.
static const char kSomeData[] = "short";
static const int kSomeDataLen = sizeof(kSomeData) - 1;
{
FileWriterDelegate writer(&file_);
ASSERT_TRUE(writer.PrepareOutput());
ASSERT_TRUE(writer.WriteBytes(kSomeData, kSomeDataLen));
}
ASSERT_EQ(kSomeDataLen, file_.GetLength());
char buf[kSomeDataLen] = {};
ASSERT_EQ(kSomeDataLen, file_.Read(0LL, buf, kSomeDataLen));
ASSERT_EQ(std::string(kSomeData), std::string(buf, kSomeDataLen));
}
} // namespace zip
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment