diff options
author | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-26 04:32:00 +0000 |
---|---|---|
committer | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-26 04:32:00 +0000 |
commit | 62116d0bcaed1987d826b88c448d0f80fa61c93f (patch) | |
tree | a65fb439417bc79bc8d668b43d5307e35abba669 /chrome/utility | |
parent | 0baa4a1cf74c5e4817f3a80fb5a624f0c7187d6c (diff) | |
download | chromium_src-62116d0bcaed1987d826b88c448d0f80fa61c93f.zip chromium_src-62116d0bcaed1987d826b88c448d0f80fa61c93f.tar.gz chromium_src-62116d0bcaed1987d826b88c448d0f80fa61c93f.tar.bz2 |
This issue is only a move and updates to the includes. It's contingent on 17101030.
For security reasons, we make the utility process parse the Picasa database PMP files. A pre-requisite of this is moving most of the PMP parsing code to utility/. A few items need to remain in common so that the code in browser/ can use the IPC structs and constants.
BUG=151701
R=gbillock
Review URL: https://chromiumcodereview.appspot.com/17408006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208625 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/utility')
-rw-r--r-- | chrome/utility/media_galleries/OWNERS | 4 | ||||
-rw-r--r-- | chrome/utility/media_galleries/picasa_album_table_reader.cc | 124 | ||||
-rw-r--r-- | chrome/utility/media_galleries/picasa_album_table_reader.h | 44 | ||||
-rw-r--r-- | chrome/utility/media_galleries/picasa_album_table_reader_unittest.cc | 117 | ||||
-rw-r--r-- | chrome/utility/media_galleries/pmp_column_reader.cc | 204 | ||||
-rw-r--r-- | chrome/utility/media_galleries/pmp_column_reader.h | 65 | ||||
-rw-r--r-- | chrome/utility/media_galleries/pmp_column_reader_unittest.cc | 192 | ||||
-rw-r--r-- | chrome/utility/media_galleries/pmp_test_helper.cc | 190 | ||||
-rw-r--r-- | chrome/utility/media_galleries/pmp_test_helper.h | 56 |
9 files changed, 996 insertions, 0 deletions
diff --git a/chrome/utility/media_galleries/OWNERS b/chrome/utility/media_galleries/OWNERS new file mode 100644 index 0000000..f441493 --- /dev/null +++ b/chrome/utility/media_galleries/OWNERS @@ -0,0 +1,4 @@ +estade@chromium.org +gbillock@chromium.org +thestig@chromium.org +vandebo@chromium.org diff --git a/chrome/utility/media_galleries/picasa_album_table_reader.cc b/chrome/utility/media_galleries/picasa_album_table_reader.cc new file mode 100644 index 0000000..ac5fc67 --- /dev/null +++ b/chrome/utility/media_galleries/picasa_album_table_reader.cc @@ -0,0 +1,124 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/media_galleries/picasa_album_table_reader.h" + +#include <algorithm> +#include <string> + +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/media_galleries/pmp_constants.h" +#include "chrome/utility/media_galleries/pmp_column_reader.h" + +namespace picasa { + +namespace { + +// |variant_time| is specified as the number of days from Dec 30, 1899. +base::Time TimeFromMicrosoftVariantTime(double variant_time) { + base::TimeDelta variant_delta = base::TimeDelta::FromMicroseconds( + static_cast<int64>(variant_time * base::Time::kMicrosecondsPerDay)); + + return base::Time::FromLocalExploded(kPmpVariantTimeEpoch) + variant_delta; +} + +} // namespace + +PicasaAlbumTableReader::PicasaAlbumTableReader( + const AlbumTableFiles& table_files) + : table_files_(table_files), + initialized_(false) { +} + +PicasaAlbumTableReader::~PicasaAlbumTableReader() {} + +const std::vector<AlbumInfo>& PicasaAlbumTableReader::folders() const { + DCHECK(initialized_); + return folders_; +} + +const std::vector<AlbumInfo>& PicasaAlbumTableReader::albums() const { + DCHECK(initialized_); + return albums_; +} + +bool PicasaAlbumTableReader::Init() { + if (initialized_) + return true; + + if (table_files_.indicator_file == base::kInvalidPlatformFileValue) + return false; + + PmpColumnReader category_column, date_column, filename_column, name_column, + token_column, uid_column; + if (!category_column.ReadFile(table_files_.category_file, PMP_TYPE_UINT32) || + !date_column.ReadFile(table_files_.date_file, PMP_TYPE_DOUBLE64) || + !filename_column.ReadFile(table_files_.filename_file, PMP_TYPE_STRING) || + !name_column.ReadFile(table_files_.name_file, PMP_TYPE_STRING) || + !token_column.ReadFile(table_files_.token_file, PMP_TYPE_STRING) || + !uid_column.ReadFile(table_files_.uid_file, PMP_TYPE_STRING)) { + return false; + } + + // In the PMP format, columns can be different lengths. The number of rows + // in the table is max of all the columns, and short columns are NULL padded. + uint32 row_count = 0; + row_count = std::max(row_count, category_column.rows_read()); + row_count = std::max(row_count, date_column.rows_read()); + row_count = std::max(row_count, filename_column.rows_read()); + row_count = std::max(row_count, name_column.rows_read()); + row_count = std::max(row_count, token_column.rows_read()); + row_count = std::max(row_count, uid_column.rows_read()); + + for (uint32 i = 0; i < row_count; i++) { + uint32 category = kAlbumCategoryInvalid; + double date = 0; + std::string name; + std::string uid; + // PMP tables often contain 'garbage' rows of deleted or auto-generated + // album-like entities. We ignore those rows. + if (!category_column.ReadUInt32(i, &category) || + !date_column.ReadDouble64(i, &date) || + !name_column.ReadString(i, &name) || name.empty() || + !uid_column.ReadString(i, &uid) || uid.empty()) { + continue; + } + + base::Time timestamp = TimeFromMicrosoftVariantTime(date); + + switch (category) { + case kAlbumCategoryAlbum: { + std::string token; + if (!token_column.ReadString(i, &token) || token.empty() || + !StartsWithASCII(token, kAlbumTokenPrefix, false)) { + continue; + } + + albums_.push_back(AlbumInfo(name, timestamp, uid, base::FilePath())); + break; + } + case kAlbumCategoryFolder: { + std::string filename; + if (!filename_column.ReadString(i, &filename) || filename.empty()) + continue; + + base::FilePath path = + base::FilePath(base::FilePath::FromUTF8Unsafe(filename)); + + folders_.push_back(AlbumInfo(name, timestamp, uid, path)); + break; + } + default: { + break; + } + } + } + + initialized_ = true; + return true; +} + +} // namespace picasa diff --git a/chrome/utility/media_galleries/picasa_album_table_reader.h b/chrome/utility/media_galleries/picasa_album_table_reader.h new file mode 100644 index 0000000..b71640d --- /dev/null +++ b/chrome/utility/media_galleries/picasa_album_table_reader.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_UTILITY_MEDIA_GALLERIES_PICASA_ALBUM_TABLE_READER_H_ +#define CHROME_UTILITY_MEDIA_GALLERIES_PICASA_ALBUM_TABLE_READER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "chrome/common/media_galleries/picasa_types.h" + +namespace picasa { + +const uint32 kAlbumCategoryAlbum = 0; +const uint32 kAlbumCategoryFolder = 2; +const uint32 kAlbumCategoryInvalid = 0xffff; // Sentinel value. + +const char kAlbumTokenPrefix[] = "]album:"; + +class PicasaAlbumTableReader { + public: + explicit PicasaAlbumTableReader(const AlbumTableFiles& table_files); + ~PicasaAlbumTableReader(); + + bool Init(); + + const std::vector<AlbumInfo>& albums() const; + const std::vector<AlbumInfo>& folders() const; + + private: + const AlbumTableFiles table_files_; + + bool initialized_; + + std::vector<AlbumInfo> albums_; + std::vector<AlbumInfo> folders_; + + DISALLOW_COPY_AND_ASSIGN(PicasaAlbumTableReader); +}; + +} // namespace picasa + +#endif // CHROME_UTILITY_MEDIA_GALLERIES_PICASA_ALBUM_TABLE_READER_H_ diff --git a/chrome/utility/media_galleries/picasa_album_table_reader_unittest.cc b/chrome/utility/media_galleries/picasa_album_table_reader_unittest.cc new file mode 100644 index 0000000..d9980b9 --- /dev/null +++ b/chrome/utility/media_galleries/picasa_album_table_reader_unittest.cc @@ -0,0 +1,117 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/media_galleries/pmp_constants.h" +#include "chrome/utility/media_galleries/picasa_album_table_reader.h" +#include "chrome/utility/media_galleries/pmp_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace picasa { + +namespace { + +base::PlatformFile OpenPlatformFile(const base::FilePath& directory_path, + const std::string& suffix) { + base::FilePath path = directory_path.Append(base::FilePath::FromUTF8Unsafe( + std::string(kPicasaAlbumTableName) + "_" + suffix)); + int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; + return base::CreatePlatformFile(path, flags, NULL, NULL); +} + +base::PlatformFile OpenColumnPlatformFile(const base::FilePath& directory_path, + const std::string& column_name) { + return OpenPlatformFile(directory_path, column_name + "." + kPmpExtension); +} + +AlbumTableFiles MakeAlbumTableFiles(const base::FilePath& directory_path) { + AlbumTableFiles files; + files.indicator_file = OpenPlatformFile(directory_path, "0"); + files.category_file = OpenColumnPlatformFile(directory_path, "category"); + files.date_file = OpenColumnPlatformFile(directory_path, "date"); + files.filename_file = OpenColumnPlatformFile(directory_path, "filename"); + files.name_file = OpenColumnPlatformFile(directory_path, "name"); + files.token_file = OpenColumnPlatformFile(directory_path, "token"); + files.uid_file = OpenColumnPlatformFile(directory_path, "uid"); +} + +TEST(PicasaAlbumTableReaderTest, FoldersAndAlbums) { + PmpTestHelper test_helper(kPicasaAlbumTableName); + ASSERT_TRUE(test_helper.Init()); + + int test_time_delta = 100; + + std::vector<uint32> category_vector; + category_vector.push_back(kAlbumCategoryFolder); + category_vector.push_back(kAlbumCategoryInvalid); + category_vector.push_back(kAlbumCategoryAlbum); + + std::vector<double> date_vector; + date_vector.push_back(0.0); + date_vector.push_back(0.0); + date_vector.push_back(0.0 + test_time_delta); + + std::string test_folder_name = "Pix4dalulz"; + std::string test_album_name = "Cats"; + + base::FilePath test_folder_path = + base::FilePath(base::FilePath::FromUTF8Unsafe("C:\\Pix4dalulz")); + + // Only folders require filenames. Tests handling of different length columns. + std::vector<std::string> filename_vector; + filename_vector.push_back(test_folder_path.AsUTF8Unsafe()); + + std::vector<std::string> name_vector; + name_vector.push_back(test_folder_name); + name_vector.push_back(""); + name_vector.push_back(test_album_name); + + std::vector<std::string> token_vector; + token_vector.push_back(""); + token_vector.push_back(""); + token_vector.push_back(std::string(kAlbumTokenPrefix) + "uid3"); + + std::vector<std::string> uid_vector; + uid_vector.push_back("uid1"); + uid_vector.push_back("uid2"); + uid_vector.push_back("uid3"); + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "category", PMP_TYPE_UINT32, category_vector)); + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "date", PMP_TYPE_DOUBLE64, date_vector)); + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "filename", PMP_TYPE_STRING, filename_vector)); + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "name", PMP_TYPE_STRING, name_vector)); + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "token", PMP_TYPE_STRING, token_vector)); + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + "uid", PMP_TYPE_STRING, uid_vector)); + + AlbumTableFiles album_table_files = + MakeAlbumTableFiles(test_helper.GetTempDirPath()); + PicasaAlbumTableReader reader(album_table_files); + + ASSERT_TRUE(reader.Init()); + CloseAlbumTableFiles(&album_table_files); + + const std::vector<AlbumInfo>& albums = reader.albums(); + const std::vector<AlbumInfo>& folders = reader.folders(); + + ASSERT_EQ(1u, albums.size()); + ASSERT_EQ(1u, folders.size()); + + EXPECT_EQ(test_album_name, albums[0].name); + EXPECT_EQ(test_folder_name, folders[0].name); + + EXPECT_EQ(test_folder_path, folders[0].path); + + base::TimeDelta time_delta = albums[0].timestamp - folders[0].timestamp; + + EXPECT_EQ(test_time_delta, time_delta.InDays()); +} + +} // namespace + +} // namespace picasa diff --git a/chrome/utility/media_galleries/pmp_column_reader.cc b/chrome/utility/media_galleries/pmp_column_reader.cc new file mode 100644 index 0000000..5150c2e --- /dev/null +++ b/chrome/utility/media_galleries/pmp_column_reader.cc @@ -0,0 +1,204 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/media_galleries/pmp_column_reader.h" + +#include <cstring> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace picasa { + +namespace { + +COMPILE_ASSERT(sizeof(double) == 8, double_must_be_8_bytes_long); +const int64 kPmpMaxFilesize = 50*1024*1024; // Arbitrary maximum of 50 MB. + +} // namespace + +PmpColumnReader::PmpColumnReader() + : length_(0), + field_type_(PMP_TYPE_INVALID), + rows_read_(0) {} + +PmpColumnReader::~PmpColumnReader() {} + +bool PmpColumnReader::ReadFile(base::PlatformFile file, + const PmpFieldType expected_type) { + DCHECK(!data_.get()); + base::ThreadRestrictions::AssertIOAllowed(); + + if (file == base::kInvalidPlatformFileValue) + return false; + + base::PlatformFileInfo info; + if (!base::GetPlatformFileInfo(file, &info)) + return false; + length_ = info.size; + + if (length_ < kPmpHeaderSize || length_ > kPmpMaxFilesize) + return false; + + data_.reset(new uint8[length_]); + + char* data_begin = reinterpret_cast<char*>(data_.get()); + + DCHECK(length_ < kint32max); // ReadFile expects an int. + + bool success = base::ReadPlatformFile(file, 0, data_begin, length_) && + ParseData(expected_type); + + // If any of the reading or parsing fails, prevent Read* calls. + if (!success) + rows_read_ = 0; + + return success; +} + +bool PmpColumnReader::ReadString(const uint32 row, std::string* result) const { + DCHECK(data_.get() != NULL); + + if (field_type_ != PMP_TYPE_STRING || row >= rows_read_) + return false; + + DCHECK_LT(row, strings_.size()); + *result = strings_[row]; + return true; +} + +bool PmpColumnReader::ReadUInt32(const uint32 row, uint32* result) const { + DCHECK(data_.get() != NULL); + + if (field_type_ != PMP_TYPE_UINT32 || row >= rows_read_) + return false; + + *result = reinterpret_cast<uint32*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadDouble64(const uint32 row, double* result) const { + DCHECK(data_.get() != NULL); + + if (field_type_ != PMP_TYPE_DOUBLE64 || row >= rows_read_) + return false; + + *result = reinterpret_cast<double*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt8(const uint32 row, uint8* result) const { + DCHECK(data_.get() != NULL); + + if (field_type_ != PMP_TYPE_UINT8 || row >= rows_read_) + return false; + + *result = reinterpret_cast<uint8*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt64(const uint32 row, uint64* result) const { + DCHECK(data_.get() != NULL); + + if (field_type_ != PMP_TYPE_UINT64 || row >= rows_read_) + return false; + + *result = reinterpret_cast<uint64*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +uint32 PmpColumnReader::rows_read() const { + DCHECK(data_.get() != NULL); + return rows_read_; +} + +bool PmpColumnReader::ParseData(const PmpFieldType expected_type) { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + // Check all magic bytes. + if (memcmp(&kPmpMagic1, &data_[kPmpMagic1Offset], sizeof(kPmpMagic1)) != 0 || + memcmp(&kPmpMagic2, &data_[kPmpMagic2Offset], sizeof(kPmpMagic2)) != 0 || + memcmp(&kPmpMagic3, &data_[kPmpMagic3Offset], sizeof(kPmpMagic3)) != 0 || + memcmp(&kPmpMagic4, &data_[kPmpMagic4Offset], sizeof(kPmpMagic4)) != 0) { + return false; + } + + uint16 field_type_data = + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType1Offset])); + + // Verify if field type matches second declaration + if (field_type_data != + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType2Offset]))) { + return false; + } + + field_type_ = static_cast<PmpFieldType>(field_type_data); + + if (field_type_ != expected_type) + return false; + + rows_read_ = *(reinterpret_cast<uint32*>(&data_[kPmpRowCountOffset])); + + // Sanity check against malicious row field. + if (rows_read_ > (kPmpMaxFilesize - kPmpHeaderSize)) + return false; + + DCHECK_GE(length_, kPmpHeaderSize); + int64 body_length = length_ - kPmpHeaderSize; + int64 expected_body_length = 0; + switch (field_type_) { + case PMP_TYPE_STRING: + expected_body_length = IndexStrings(); + break; + case PMP_TYPE_UINT32: + expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint32); + break; + case PMP_TYPE_DOUBLE64: + expected_body_length = static_cast<int64>(rows_read_) * sizeof(double); + break; + case PMP_TYPE_UINT8: + expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint8); + break; + case PMP_TYPE_UINT64: + expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint64); + break; + default: + return false; + break; + } + + return body_length == expected_body_length; +} + +int64 PmpColumnReader::IndexStrings() { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + strings_.reserve(rows_read_); + + int64 bytes_parsed = kPmpHeaderSize; + const uint8* data_cursor = data_.get() + kPmpHeaderSize; + + while (strings_.size() < rows_read_) { + const uint8* string_end = static_cast<const uint8*>( + memchr(data_cursor, '\0', length_ - bytes_parsed)); + + // Fail if cannot find null termination. String runs on past file end. + if (string_end == NULL) + return -1; + + // Length of string. (+1 to include the termination character). + ptrdiff_t length_in_bytes = string_end - data_cursor + 1; + + strings_.push_back(reinterpret_cast<const char*>(data_cursor)); + data_cursor += length_in_bytes; + bytes_parsed += length_in_bytes; + } + + return bytes_parsed - kPmpHeaderSize; +} + +} // namespace picasa diff --git a/chrome/utility/media_galleries/pmp_column_reader.h b/chrome/utility/media_galleries/pmp_column_reader.h new file mode 100644 index 0000000..b257e61 --- /dev/null +++ b/chrome/utility/media_galleries/pmp_column_reader.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_UTILITY_MEDIA_GALLERIES_PMP_COLUMN_READER_H_ +#define CHROME_UTILITY_MEDIA_GALLERIES_PMP_COLUMN_READER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "chrome/common/media_galleries/pmp_constants.h" + +namespace base { +class FilePath; +} + +namespace picasa { + +// Reads a single PMP column from a file. +class PmpColumnReader { + public: + PmpColumnReader(); + virtual ~PmpColumnReader(); + + // Returns true if read successfully. + // |rows_read| is undefined if returns false. + bool ReadFile(base::PlatformFile file, const PmpFieldType expected_type); + + // These functions read the value of that |row| into |result|. + // Functions return false if the column is of the wrong type or the row + // is out of range. May only be called after successful ReadColumn. + bool ReadString(const uint32 row, std::string* result) const; + bool ReadUInt32(const uint32 row, uint32* result) const; + bool ReadDouble64(const uint32 row, double* result) const; + bool ReadUInt8(const uint32 row, uint8* result) const; + bool ReadUInt64(const uint32 row, uint64* result) const; + + // May only be called after successful ReadColumn. + uint32 rows_read() const; + + private: + bool ParseData(const PmpFieldType expected_type); + // Returns the number of bytes parsed in the body, or, -1 on failure. + int64 IndexStrings(); + + // Source data + scoped_ptr<uint8[]> data_; + int64 length_; + + // Header data + PmpFieldType field_type_; + uint32 rows_read_; + + // Index of string start locations if fields are strings. Empty otherwise. + std::vector<const char*> strings_; + + DISALLOW_COPY_AND_ASSIGN(PmpColumnReader); +}; + +} // namespace picasa + +#endif // CHROME_UTILITY_MEDIA_GALLERIES_PMP_COLUMN_READER_H_ diff --git a/chrome/utility/media_galleries/pmp_column_reader_unittest.cc b/chrome/utility/media_galleries/pmp_column_reader_unittest.cc new file mode 100644 index 0000000..a9cfa5b --- /dev/null +++ b/chrome/utility/media_galleries/pmp_column_reader_unittest.cc @@ -0,0 +1,192 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <vector> + +#include "chrome/common/media_galleries/pmp_constants.h" +#include "chrome/utility/media_galleries/pmp_column_reader.h" +#include "chrome/utility/media_galleries/pmp_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using picasa::PmpColumnReader; +using picasa::PmpTestHelper; + +// Overridden version of Read method to make test code templatable. +bool DoRead(const PmpColumnReader* reader, uint32 row, std::string* target) { + return reader->ReadString(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint32* target) { + return reader->ReadUInt32(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, double* target) { + return reader->ReadDouble64(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint8* target) { + return reader->ReadUInt8(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint64* target) { + return reader->ReadUInt64(row, target); +} + +// TestValid +template<class T> +void TestValid(const picasa::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper("test"); + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader; + std::vector<uint8> data = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + ASSERT_TRUE(test_helper.InitColumnReaderFromBytes( + &reader, data, field_type)); + EXPECT_EQ(elems.size(), reader.rows_read()); + + for (uint32 i = 0; i < elems.size() && i < reader.rows_read(); i++) { + T target; + EXPECT_TRUE(DoRead(&reader, i, &target)); + EXPECT_EQ(elems[i], target); + } +} + +template<class T> +void TestMalformed(const picasa::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper("test"); + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader_too_few_declared_rows; + std::vector<uint8> data_too_few_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()-1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_too_few_declared_rows, + data_too_few_declared_rows, + field_type)); + + PmpColumnReader reader_too_many_declared_rows; + std::vector<uint8> data_too_many_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()+1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_too_many_declared_rows, + data_too_many_declared_rows, + field_type)); + + PmpColumnReader reader_truncated; + std::vector<uint8> data_truncated = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_truncated.resize(data_truncated.size()-10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_truncated, data_truncated, field_type)); + + PmpColumnReader reader_padded; + std::vector<uint8> data_padded = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_padded.resize(data_padded.size()+10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_padded, data_padded, field_type)); +} + +template<class T> +void TestPrimitive(const picasa::PmpFieldType field_type) { + // Make an ascending vector of the primitive. + uint32 n = 100; + std::vector<T> data(n, 0); + for (uint32 i = 0; i < n; i++) { + data[i] = i*3; + } + + TestValid<T>(field_type, data); + TestMalformed<T>(field_type, data); +} + + +TEST(PmpColumnReaderTest, HeaderParsingAndValidation) { + PmpTestHelper test_helper("test"); + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader_good_header; + std::vector<uint8> good_header = + PmpTestHelper::MakeHeader(picasa::PMP_TYPE_STRING, 0); + EXPECT_TRUE(test_helper.InitColumnReaderFromBytes( + &reader_good_header, + good_header, + picasa::PMP_TYPE_STRING)); + EXPECT_EQ(0U, reader_good_header.rows_read()) << + "Read non-zero rows from header-only data."; + + PmpColumnReader reader_bad_magic_bytes; + std::vector<uint8> bad_magic_bytes = + PmpTestHelper::MakeHeader(picasa::PMP_TYPE_STRING, 0); + bad_magic_bytes[0] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_bad_magic_bytes, + bad_magic_bytes, + picasa::PMP_TYPE_STRING)); + + PmpColumnReader reader_inconsistent_types; + std::vector<uint8> inconsistent_type = + PmpTestHelper::MakeHeader(picasa::PMP_TYPE_STRING, 0); + inconsistent_type[picasa::kPmpFieldType1Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_inconsistent_types, + inconsistent_type, + picasa::PMP_TYPE_STRING)); + + PmpColumnReader reader_invalid_type; + std::vector<uint8> invalid_type = + PmpTestHelper::MakeHeader(picasa::PMP_TYPE_STRING, 0); + invalid_type[picasa::kPmpFieldType1Offset] = 0xff; + invalid_type[picasa::kPmpFieldType2Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_invalid_type, + invalid_type, + picasa::PMP_TYPE_STRING)); + + PmpColumnReader reader_incomplete_header; + std::vector<uint8> incomplete_header = + PmpTestHelper::MakeHeader(picasa::PMP_TYPE_STRING, 0); + incomplete_header.resize(10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader_incomplete_header, + incomplete_header, + picasa::PMP_TYPE_STRING)); +} + +TEST(PmpColumnReaderTest, StringParsing) { + std::vector<std::string> empty_strings(100, ""); + + // Test empty strings read okay. + TestValid(picasa::PMP_TYPE_STRING, empty_strings); + + std::vector<std::string> mixed_strings; + mixed_strings.push_back(""); + mixed_strings.push_back("Hello"); + mixed_strings.push_back("World"); + mixed_strings.push_back(""); + mixed_strings.push_back("123123"); + mixed_strings.push_back("Q"); + mixed_strings.push_back(""); + + // Test that a mixed set of strings read correctly. + TestValid(picasa::PMP_TYPE_STRING, mixed_strings); + + // Test with the data messed up in a variety of ways. + TestMalformed(picasa::PMP_TYPE_STRING, mixed_strings); +} + +TEST(PmpColumnReaderTest, PrimitiveParsing) { + TestPrimitive<uint32>(picasa::PMP_TYPE_UINT32); + TestPrimitive<double>(picasa::PMP_TYPE_DOUBLE64); + TestPrimitive<uint8>(picasa::PMP_TYPE_UINT8); + TestPrimitive<uint64>(picasa::PMP_TYPE_UINT64); +} + +} // namespace diff --git a/chrome/utility/media_galleries/pmp_test_helper.cc b/chrome/utility/media_galleries/pmp_test_helper.cc new file mode 100644 index 0000000..bc15c4f --- /dev/null +++ b/chrome/utility/media_galleries/pmp_test_helper.cc @@ -0,0 +1,190 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/media_galleries/pmp_test_helper.h" + +#include <algorithm> +#include <iterator> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/media_galleries/picasa_types.h" +#include "chrome/utility/media_galleries/pmp_column_reader.h" + +namespace picasa { + +namespace { + +bool WriteToFile(const base::FilePath& path, std::vector<uint8> data) { + // Cast for usage in WriteFile function + const char* data_char = reinterpret_cast<const char*>(&data[0]); + size_t bytes_written = file_util::WriteFile(path, data_char, data.size()); + return (bytes_written == data.size()); +} + +// Flatten a vector of elements into an array of bytes. +template<class T> +std::vector<uint8> Flatten(const std::vector<T>& elems) { + if (elems.empty()) + return std::vector<uint8>(); + + const uint8* elems0 = reinterpret_cast<const uint8*>(&elems[0]); + std::vector<uint8> data_body(elems0, elems0 + sizeof(T) * elems.size()); + + return data_body; +} + +// Custom specialization for std::string. +template<> +std::vector<uint8> Flatten(const std::vector<std::string>& strings) { + std::vector<uint8> totalchars; + + for (std::vector<std::string>::const_iterator it = strings.begin(); + it != strings.end(); ++it) { + std::copy(it->begin(), it->end(), std::back_inserter(totalchars)); + totalchars.push_back('\0'); // Add the null termination too. + } + + return totalchars; +} + +// Returns a new vector with the concatenated contents of |a| and |b|. +std::vector<uint8> CombinedVectors(const std::vector<uint8>& a, + const std::vector<uint8>& b) { + std::vector<uint8> total; + + std::copy(a.begin(), a.end(), std::back_inserter(total)); + std::copy(b.begin(), b.end(), std::back_inserter(total)); + + return total; +} + +} // namespace + +PmpTestHelper::PmpTestHelper(const std::string& table_name) + : table_name_(table_name) { +} + +bool PmpTestHelper::Init() { + if (!temp_dir_.CreateUniqueTempDir() || !temp_dir_.IsValid()) + return false; + + base::FilePath indicator_path = temp_dir_.path().Append( + base::FilePath::FromUTF8Unsafe(table_name_ + "_0")); + + return file_util::WriteFile(indicator_path, NULL, 0) == 0; +} + +base::FilePath PmpTestHelper::GetTempDirPath() { + DCHECK(temp_dir_.IsValid()); + return temp_dir_.path(); +} + +template<class T> +bool PmpTestHelper::WriteColumnFileFromVector( + const std::string& column_name, const PmpFieldType field_type, + const std::vector<T>& elements_vector) { + DCHECK(temp_dir_.IsValid()); + + std::string file_name = table_name_ + "_" + column_name + "." + kPmpExtension; + + base::FilePath path = temp_dir_.path().Append( + base::FilePath::FromUTF8Unsafe(file_name)); + + std::vector<uint8> data = PmpTestHelper::MakeHeaderAndBody( + field_type, elements_vector.size(), elements_vector); + + return WriteToFile(path, data); +} + +// Explicit Instantiation for all the valid types. +template bool PmpTestHelper::WriteColumnFileFromVector<std::string>( + const std::string&, const PmpFieldType, const std::vector<std::string>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint32>( + const std::string&, const PmpFieldType, const std::vector<uint32>&); +template bool PmpTestHelper::WriteColumnFileFromVector<double>( + const std::string&, const PmpFieldType, const std::vector<double>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint8>( + const std::string&, const PmpFieldType, const std::vector<uint8>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint64>( + const std::string&, const PmpFieldType, const std::vector<uint64>&); + +bool PmpTestHelper::InitColumnReaderFromBytes( + PmpColumnReader* const reader, + const std::vector<uint8>& data, + const PmpFieldType expected_type) { + DCHECK(temp_dir_.IsValid()); + + base::FilePath temp_path; + + if (!file_util::CreateTemporaryFileInDir(temp_dir_.path(), &temp_path) + || !WriteToFile(temp_path, data)) { + return false; + } + + int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; + base::PlatformFile platform_file = + base::CreatePlatformFile(temp_path, flags, NULL, NULL); + if (platform_file == base::kInvalidPlatformFileValue) + return false; + + bool read_success = reader->ReadFile(platform_file, expected_type); + + base::ClosePlatformFile(platform_file); + file_util::Delete(temp_path, false /* recursive */); + + return read_success; +} + +// Return a vector so we don't have to worry about memory management. +std::vector<uint8> PmpTestHelper::MakeHeader(const PmpFieldType field_type, + const uint32 row_count) { + std::vector<uint8> header(picasa::kPmpHeaderSize); + + // Copy in magic bytes. + memcpy(&header[picasa::kPmpMagic1Offset], &picasa::kPmpMagic1, + sizeof(picasa::kPmpMagic1)); + memcpy(&header[picasa::kPmpMagic2Offset], &picasa::kPmpMagic2, + sizeof(picasa::kPmpMagic2)); + memcpy(&header[picasa::kPmpMagic3Offset], &picasa::kPmpMagic3, + sizeof(picasa::kPmpMagic3)); + memcpy(&header[picasa::kPmpMagic4Offset], &picasa::kPmpMagic4, + sizeof(picasa::kPmpMagic4)); + + // Copy in field type. + uint16 field_type_short = static_cast<uint16>(field_type); + memcpy(&header[picasa::kPmpFieldType1Offset], &field_type_short, + sizeof(uint16)); + memcpy(&header[picasa::kPmpFieldType2Offset], &field_type_short, + sizeof(uint16)); + + // Copy in row count. + memcpy(&header[picasa::kPmpRowCountOffset], &row_count, sizeof(uint32)); + + return header; +} + +template<class T> +std::vector<uint8> PmpTestHelper::MakeHeaderAndBody( + const PmpFieldType field_type, const uint32 row_count, + const std::vector<T>& elems) { + return CombinedVectors(PmpTestHelper::MakeHeader(field_type, row_count), + Flatten(elems)); +} + +// Explicit Instantiation for all the valid types. +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<std::string>( + const PmpFieldType, const uint32, const std::vector<std::string>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint32>( + const PmpFieldType, const uint32, const std::vector<uint32>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<double>( + const PmpFieldType, const uint32, const std::vector<double>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint8>( + const PmpFieldType, const uint32, const std::vector<uint8>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint64>( + const PmpFieldType, const uint32, const std::vector<uint64>&); + +} // namespace picasa diff --git a/chrome/utility/media_galleries/pmp_test_helper.h b/chrome/utility/media_galleries/pmp_test_helper.h new file mode 100644 index 0000000..2878efe --- /dev/null +++ b/chrome/utility/media_galleries/pmp_test_helper.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_UTILITY_MEDIA_GALLERIES_PMP_TEST_HELPER_H_ +#define CHROME_UTILITY_MEDIA_GALLERIES_PMP_TEST_HELPER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/scoped_temp_dir.h" +#include "chrome/common/media_galleries/pmp_constants.h" + +namespace base { +class FilePath; +} // namespace base + +namespace picasa { + +class PmpColumnReader; + +// A helper class used for unit tests only +class PmpTestHelper { + public: + explicit PmpTestHelper(const std::string& table_name); + + bool Init(); + + base::FilePath GetTempDirPath(); + + template<class T> + bool WriteColumnFileFromVector(const std::string& column_name, + const PmpFieldType field_type, + const std::vector<T>& elements_vector); + + bool InitColumnReaderFromBytes(PmpColumnReader* const reader, + const std::vector<uint8>& data, + const PmpFieldType expected_type); + + static std::vector<uint8> MakeHeader(const PmpFieldType field_type, + const uint32 row_count); + + template<class T> + static std::vector<uint8> MakeHeaderAndBody(const PmpFieldType field_type, + const uint32 row_count, + const std::vector<T>& elems); + + private: + std::string table_name_; + base::ScopedTempDir temp_dir_; +}; + +} // namespace picasa + +#endif // CHROME_UTILITY_MEDIA_GALLERIES_PMP_TEST_HELPER_H_ |