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 @@ ...@@ -23,6 +23,21 @@
namespace zip { 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 // 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 // class is to scan entries in a zip file and extract them. The code will
// look like: // look like:
...@@ -141,6 +156,9 @@ class ZipReader { ...@@ -141,6 +156,9 @@ class ZipReader {
// success. On failure, current_entry_info() becomes NULL. // success. On failure, current_entry_info() becomes NULL.
bool LocateAndOpenEntry(const base::FilePath& path_in_zip); 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 // Extracts the current entry to the given output file path. If the
// current file is a directory, just creates a directory // current file is a directory, just creates a directory
// instead. Returns true on success. OpenCurrentEntryInZip() must be // instead. Returns true on success. OpenCurrentEntryInZip() must be
...@@ -148,7 +166,8 @@ class ZipReader { ...@@ -148,7 +166,8 @@ class ZipReader {
// //
// This function preserves the timestamp of the original entry. If that // This function preserves the timestamp of the original entry. If that
// timestamp is not valid, the timestamp will be set to the current time. // 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. // Asynchronously extracts the current entry to the given output file path.
// If the current entry is a directory it just creates the directory // If the current entry is a directory it just creates the directory
...@@ -174,13 +193,11 @@ class ZipReader { ...@@ -174,13 +193,11 @@ class ZipReader {
// This function preserves the timestamp of the original entry. If that // This function preserves the timestamp of the original entry. If that
// timestamp is not valid, the timestamp will be set to the current time. // timestamp is not valid, the timestamp will be set to the current time.
bool ExtractCurrentEntryIntoDirectory( 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 platform file.
// Extracts the current entry by writing directly to a file descriptor. // Does not close the file. Returns true on success.
// Does not close the file descriptor. Returns true on success. bool ExtractCurrentEntryToFile(base::File* file) const;
bool ExtractCurrentEntryToFd(int fd);
#endif
// Extracts the current entry into memory. If the current entry is a directory // 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 // the |output| parameter is set to the empty string. If the current entry is
...@@ -232,6 +249,30 @@ class ZipReader { ...@@ -232,6 +249,30 @@ class ZipReader {
DISALLOW_COPY_AND_ASSIGN(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 } // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ #endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
...@@ -18,10 +18,14 @@ ...@@ -18,10 +18,14 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h" #include "testing/platform_test.h"
#include "third_party/zlib/google/zip_internal.h" #include "third_party/zlib/google/zip_internal.h"
using ::testing::Return;
using ::testing::_;
namespace { namespace {
const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
...@@ -47,6 +51,8 @@ class FileWrapper { ...@@ -47,6 +51,8 @@ class FileWrapper {
base::PlatformFile platform_file() { return file_.GetPlatformFile(); } base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
base::File* file() { return &file_; }
private: private:
base::File file_; base::File file_;
}; };
...@@ -93,6 +99,12 @@ class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> { ...@@ -93,6 +99,12 @@ class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> {
int64 current_progress_; int64 current_progress_;
}; };
class MockWriterDelegate : public zip::WriterDelegate {
public:
MOCK_METHOD0(PrepareOutput, bool());
MOCK_METHOD2(WriteBytes, bool(const char*, int));
};
} // namespace } // namespace
namespace zip { namespace zip {
...@@ -287,8 +299,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) { ...@@ -287,8 +299,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) {
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
} }
#if defined(OS_POSIX) TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFile_RegularFile) {
TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
ZipReader reader; ZipReader reader;
FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY); FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file())); ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
...@@ -296,18 +307,16 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) { ...@@ -296,18 +307,16 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
base::FilePath out_path = test_dir_.AppendASCII("quux.txt"); base::FilePath out_path = test_dir_.AppendASCII("quux.txt");
FileWrapper out_fd_w(out_path, FileWrapper::READ_WRITE); FileWrapper out_fd_w(out_path, FileWrapper::READ_WRITE);
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); 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. // Read the output file and compute the MD5.
std::string output; std::string output;
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), ASSERT_TRUE(base::ReadFileToString(out_path, &output));
&output));
const std::string md5 = base::MD5String(output); const std::string md5 = base::MD5String(output);
EXPECT_EQ(kQuuxExpectedMD5, md5); EXPECT_EQ(kQuuxExpectedMD5, md5);
// quux.txt should be larger than kZipBufSize so that we can exercise // quux.txt should be larger than kZipBufSize so that we can exercise
// the loop in ExtractCurrentEntry(). // the loop in ExtractCurrentEntry().
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
} }
#endif
TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) { TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) {
ZipReader reader; ZipReader reader;
...@@ -582,4 +591,97 @@ TEST_F(ZipReaderTest, DISABLED_LeakDetectionTest) { ...@@ -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 } // 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