diff options
author | grt <grt@chromium.org> | 2015-03-17 11:53:32 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-17 18:54:39 +0000 |
commit | 2919be01ff758875fc7161f6b41f4461518c1213 (patch) | |
tree | 786c73170ad410b69be9377a6dce5d85dd3d9d79 /third_party/zlib | |
parent | 4a4b55844b0cff76d33734555fd3267f0da7826e (diff) | |
download | chromium_src-2919be01ff758875fc7161f6b41f4461518c1213.zip chromium_src-2919be01ff758875fc7161f6b41f4461518c1213.tar.gz chromium_src-2919be01ff758875fc7161f6b41f4461518c1213.tar.bz2 |
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}
Diffstat (limited to 'third_party/zlib')
-rw-r--r-- | third_party/zlib/google/zip_reader.cc | 250 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader.h | 55 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader_unittest.cc | 114 |
3 files changed, 318 insertions, 101 deletions
diff --git a/third_party/zlib/google/zip_reader.cc b/third_party/zlib/google/zip_reader.cc index a1dddfb..59d96da 100644 --- a/third_party/zlib/google/zip_reader.cc +++ b/third_party/zlib/google/zip_reader.cc @@ -23,6 +23,103 @@ namespace zip { +namespace { + +// FilePathWriterDelegate ------------------------------------------------------ + +// A writer delegate that writes a file at a given path. +class FilePathWriterDelegate : public WriterDelegate { + public: + explicit FilePathWriterDelegate(const base::FilePath& output_file_path); + ~FilePathWriterDelegate() override; + + // WriterDelegate methods: + + // Creates the output file and any necessary intermediate directories. + bool PrepareOutput() override; + + // Writes |num_bytes| bytes of |data| to the file, returning false if not all + // bytes could be written. + bool WriteBytes(const char* data, int num_bytes) override; + + private: + base::FilePath output_file_path_; + base::File file_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWriterDelegate); +}; + +FilePathWriterDelegate::FilePathWriterDelegate( + const base::FilePath& output_file_path) + : output_file_path_(output_file_path) { +} + +FilePathWriterDelegate::~FilePathWriterDelegate() { +} + +bool FilePathWriterDelegate::PrepareOutput() { + // We can't rely on parent directory entries being specified in the + // zip, so we make sure they are created. + if (!base::CreateDirectory(output_file_path_.DirName())) + return false; + + file_.Initialize(output_file_path_, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + return file_.IsValid(); +} + +bool FilePathWriterDelegate::WriteBytes(const char* data, int num_bytes) { + return num_bytes == file_.WriteAtCurrentPos(data, num_bytes); +} + + +// StringWriterDelegate -------------------------------------------------------- + +// A writer delegate that writes no more than |max_read_bytes| to a given +// std::string. +class StringWriterDelegate : public WriterDelegate { + public: + StringWriterDelegate(size_t max_read_bytes, std::string* output); + ~StringWriterDelegate() override; + + // WriterDelegate methods: + + // Returns true. + bool PrepareOutput() override; + + // Appends |num_bytes| bytes from |data| to the output string. Returns false + // if |num_bytes| will cause the string to exceed |max_read_bytes|. + bool WriteBytes(const char* data, int num_bytes) override; + + private: + size_t max_read_bytes_; + std::string* output_; + + DISALLOW_COPY_AND_ASSIGN(StringWriterDelegate); +}; + +StringWriterDelegate::StringWriterDelegate(size_t max_read_bytes, + std::string* output) + : max_read_bytes_(max_read_bytes), + output_(output) { +} + +StringWriterDelegate::~StringWriterDelegate() { +} + +bool StringWriterDelegate::PrepareOutput() { + return true; +} + +bool StringWriterDelegate::WriteBytes(const char* data, int num_bytes) { + if (output_->size() + num_bytes > max_read_bytes_) + return false; + output_->append(data, num_bytes); + return true; +} + +} // namespace + // TODO(satorux): The implementation assumes that file names in zip files // are encoded in UTF-8. This is true for zip files created by Zip() // function in zip.h, but not true for user-supplied random zip files. @@ -187,33 +284,20 @@ bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) { return OpenCurrentEntryInZip(); } -bool ZipReader::ExtractCurrentEntryToFilePath( - const base::FilePath& output_file_path) { +bool ZipReader::ExtractCurrentEntry(WriterDelegate* delegate) const { DCHECK(zip_file_); - // If this is a directory, just create it and return. - if (current_entry_info()->is_directory()) - return base::CreateDirectory(output_file_path); - const int open_result = unzOpenCurrentFile(zip_file_); if (open_result != UNZ_OK) return false; - // We can't rely on parent directory entries being specified in the - // zip, so we make sure they are created. - base::FilePath output_dir_path = output_file_path.DirName(); - if (!base::CreateDirectory(output_dir_path)) - return false; - - base::File file(output_file_path, - base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); - if (!file.IsValid()) + if (!delegate->PrepareOutput()) return false; bool success = true; // This becomes false when something bad happens. + scoped_ptr<char[]> buf(new char[internal::kZipBufSize]); while (true) { - char buf[internal::kZipBufSize]; - const int num_bytes_read = unzReadCurrentFile(zip_file_, buf, + const int num_bytes_read = unzReadCurrentFile(zip_file_, buf.get(), internal::kZipBufSize); if (num_bytes_read == 0) { // Reached the end of the file. @@ -223,21 +307,39 @@ bool ZipReader::ExtractCurrentEntryToFilePath( success = false; break; } else if (num_bytes_read > 0) { - // Some data is read. Write it to the output file. - if (num_bytes_read != file.WriteAtCurrentPos(buf, num_bytes_read)) { + // Some data is read. + if (!delegate->WriteBytes(buf.get(), num_bytes_read)) { success = false; break; } } } - file.Close(); unzCloseCurrentFile(zip_file_); - if (current_entry_info()->last_modified() != base::Time::UnixEpoch()) + return success; +} + +bool ZipReader::ExtractCurrentEntryToFilePath( + const base::FilePath& output_file_path) const { + DCHECK(zip_file_); + + // If this is a directory, just create it and return. + if (current_entry_info()->is_directory()) + return base::CreateDirectory(output_file_path); + + bool success = false; + { + FilePathWriterDelegate writer(output_file_path); + success = ExtractCurrentEntry(&writer); + } + + if (success && + current_entry_info()->last_modified() != base::Time::UnixEpoch()) { base::TouchFile(output_file_path, base::Time::Now(), current_entry_info()->last_modified()); + } return success; } @@ -296,7 +398,7 @@ void ZipReader::ExtractCurrentEntryToFilePathAsync( } bool ZipReader::ExtractCurrentEntryIntoDirectory( - const base::FilePath& output_directory_path) { + const base::FilePath& output_directory_path) const { DCHECK(current_entry_info_.get()); base::FilePath output_file_path = output_directory_path.Append( @@ -304,61 +406,29 @@ bool ZipReader::ExtractCurrentEntryIntoDirectory( return ExtractCurrentEntryToFilePath(output_file_path); } -#if defined(OS_POSIX) -bool ZipReader::ExtractCurrentEntryToFd(const int fd) { +bool ZipReader::ExtractCurrentEntryToFile(base::File* file) const { DCHECK(zip_file_); - // If this is a directory, there's nothing to extract to the file descriptor, - // so return false. + // If this is a directory, there's nothing to extract to the file, so return + // false. if (current_entry_info()->is_directory()) return false; - const int open_result = unzOpenCurrentFile(zip_file_); - if (open_result != UNZ_OK) - return false; - - bool success = true; // This becomes false when something bad happens. - while (true) { - char buf[internal::kZipBufSize]; - const int num_bytes_read = unzReadCurrentFile(zip_file_, buf, - internal::kZipBufSize); - if (num_bytes_read == 0) { - // Reached the end of the file. - break; - } else if (num_bytes_read < 0) { - // If num_bytes_read < 0, then it's a specific UNZ_* error code. - success = false; - break; - } else if (num_bytes_read > 0) { - // Some data is read. Write it to the output file descriptor. - if (!base::WriteFileDescriptor(fd, buf, num_bytes_read)) { - success = false; - break; - } - } - } - - unzCloseCurrentFile(zip_file_); - return success; + FileWriterDelegate writer(file); + return ExtractCurrentEntry(&writer); } -#endif // defined(OS_POSIX) -bool ZipReader::ExtractCurrentEntryToString( - size_t max_read_bytes, - std::string* output) const { +bool ZipReader::ExtractCurrentEntryToString(size_t max_read_bytes, + std::string* output) const { DCHECK(output); DCHECK(zip_file_); - DCHECK(max_read_bytes != 0); + DCHECK_NE(0U, max_read_bytes); if (current_entry_info()->is_directory()) { output->clear(); return true; } - const int open_result = unzOpenCurrentFile(zip_file_); - if (open_result != UNZ_OK) - return false; - // The original_size() is the best hint for the real size, so it saves // doing reallocations for the common case when the uncompressed size is // correct. However, we need to assume that the uncompressed size could be @@ -368,32 +438,11 @@ bool ZipReader::ExtractCurrentEntryToString( static_cast<int64>(max_read_bytes), current_entry_info()->original_size()))); - bool success = true; // This becomes false when something bad happens. - char buf[internal::kZipBufSize]; - while (true) { - const int num_bytes_read = unzReadCurrentFile(zip_file_, buf, - internal::kZipBufSize); - if (num_bytes_read == 0) { - // Reached the end of the file. - break; - } else if (num_bytes_read < 0) { - // If num_bytes_read < 0, then it's a specific UNZ_* error code. - success = false; - break; - } else if (num_bytes_read > 0) { - if (contents.size() + num_bytes_read > max_read_bytes) { - success = false; - break; - } - contents.append(buf, num_bytes_read); - } - } - - unzCloseCurrentFile(zip_file_); - if (success) - output->swap(contents); - - return success; + StringWriterDelegate writer(max_read_bytes, &contents); + if (!ExtractCurrentEntry(&writer)) + return false; + output->swap(contents); + return true; } bool ZipReader::OpenInternal() { @@ -461,5 +510,30 @@ void ZipReader::ExtractChunk(base::File output_file, } } +// FileWriterDelegate ---------------------------------------------------------- + +FileWriterDelegate::FileWriterDelegate(base::File* file) + : file_(file), + file_length_(0) { +} + +FileWriterDelegate::~FileWriterDelegate() { +#if !defined(NDEBUG) + const bool success = +#endif + file_->SetLength(file_length_); + DPLOG_IF(ERROR, !success) << "Failed updating length of written file"; +} + +bool FileWriterDelegate::PrepareOutput() { + return file_->Seek(base::File::FROM_BEGIN, 0) >= 0; +} + +bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) { + int bytes_written = file_->WriteAtCurrentPos(data, num_bytes); + if (bytes_written > 0) + file_length_ += bytes_written; + return bytes_written == num_bytes; +} } // namespace zip diff --git a/third_party/zlib/google/zip_reader.h b/third_party/zlib/google/zip_reader.h index 9280f23..da6cc93 100644 --- a/third_party/zlib/google/zip_reader.h +++ b/third_party/zlib/google/zip_reader.h @@ -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_ diff --git a/third_party/zlib/google/zip_reader_unittest.cc b/third_party/zlib/google/zip_reader_unittest.cc index d4bb579..89b4ac5 100644 --- a/third_party/zlib/google/zip_reader_unittest.cc +++ b/third_party/zlib/google/zip_reader_unittest.cc @@ -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 |