summaryrefslogtreecommitdiffstats
path: root/third_party/zlib
diff options
context:
space:
mode:
authorgrt <grt@chromium.org>2015-03-17 11:53:32 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-17 18:54:39 +0000
commit2919be01ff758875fc7161f6b41f4461518c1213 (patch)
tree786c73170ad410b69be9377a6dce5d85dd3d9d79 /third_party/zlib
parent4a4b55844b0cff76d33734555fd3267f0da7826e (diff)
downloadchromium_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.cc250
-rw-r--r--third_party/zlib/google/zip_reader.h55
-rw-r--r--third_party/zlib/google/zip_reader_unittest.cc114
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