summaryrefslogtreecommitdiffstats
path: root/chrome/utility
diff options
context:
space:
mode:
authortommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-26 04:32:00 +0000
committertommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-26 04:32:00 +0000
commit62116d0bcaed1987d826b88c448d0f80fa61c93f (patch)
treea65fb439417bc79bc8d668b43d5307e35abba669 /chrome/utility
parent0baa4a1cf74c5e4817f3a80fb5a624f0c7187d6c (diff)
downloadchromium_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/OWNERS4
-rw-r--r--chrome/utility/media_galleries/picasa_album_table_reader.cc124
-rw-r--r--chrome/utility/media_galleries/picasa_album_table_reader.h44
-rw-r--r--chrome/utility/media_galleries/picasa_album_table_reader_unittest.cc117
-rw-r--r--chrome/utility/media_galleries/pmp_column_reader.cc204
-rw-r--r--chrome/utility/media_galleries/pmp_column_reader.h65
-rw-r--r--chrome/utility/media_galleries/pmp_column_reader_unittest.cc192
-rw-r--r--chrome/utility/media_galleries/pmp_test_helper.cc190
-rw-r--r--chrome/utility/media_galleries/pmp_test_helper.h56
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_