1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/utility/media_galleries/pmp_column_reader.h"
6
7#include <cstring>
8
9#include "base/file_util.h"
10#include "base/logging.h"
11#include "base/threading/thread_restrictions.h"
12
13namespace picasa {
14
15namespace {
16
17COMPILE_ASSERT(sizeof(double) == 8, double_must_be_8_bytes_long);
18const int64 kPmpMaxFilesize = 50*1024*1024;  // Arbitrary maximum of 50 MB.
19
20}  // namespace
21
22PmpColumnReader::PmpColumnReader()
23    : length_(0),
24      field_type_(PMP_TYPE_INVALID),
25      rows_read_(0) {}
26
27PmpColumnReader::~PmpColumnReader() {}
28
29bool PmpColumnReader::ReadFile(base::PlatformFile file,
30                               const PmpFieldType expected_type) {
31  DCHECK(!data_.get());
32  base::ThreadRestrictions::AssertIOAllowed();
33
34  if (file == base::kInvalidPlatformFileValue)
35    return false;
36
37  base::PlatformFileInfo info;
38  if (!base::GetPlatformFileInfo(file, &info))
39    return false;
40  length_ = info.size;
41
42  if (length_ < kPmpHeaderSize || length_ > kPmpMaxFilesize)
43    return false;
44
45  data_.reset(new uint8[length_]);
46
47  char* data_begin = reinterpret_cast<char*>(data_.get());
48
49  DCHECK(length_ < kint32max);  // ReadFile expects an int.
50
51  bool success = base::ReadPlatformFile(file, 0, data_begin, length_) &&
52                 ParseData(expected_type);
53
54  // If any of the reading or parsing fails, prevent Read* calls.
55  if (!success)
56    rows_read_ = 0;
57
58  return success;
59}
60
61bool PmpColumnReader::ReadString(const uint32 row, std::string* result) const {
62  DCHECK(data_.get() != NULL);
63
64  if (field_type_ != PMP_TYPE_STRING || row >= rows_read_)
65    return false;
66
67  DCHECK_LT(row, strings_.size());
68  *result = strings_[row];
69  return true;
70}
71
72bool PmpColumnReader::ReadUInt32(const uint32 row, uint32* result) const {
73  DCHECK(data_.get() != NULL);
74
75  if (field_type_ != PMP_TYPE_UINT32 || row >= rows_read_)
76    return false;
77
78  *result = reinterpret_cast<uint32*>(data_.get() + kPmpHeaderSize)[row];
79  return true;
80}
81
82bool PmpColumnReader::ReadDouble64(const uint32 row, double* result) const {
83  DCHECK(data_.get() != NULL);
84
85  if (field_type_ != PMP_TYPE_DOUBLE64 || row >= rows_read_)
86    return false;
87
88  *result = reinterpret_cast<double*>(data_.get() + kPmpHeaderSize)[row];
89  return true;
90}
91
92bool PmpColumnReader::ReadUInt8(const uint32 row, uint8* result) const {
93  DCHECK(data_.get() != NULL);
94
95  if (field_type_ != PMP_TYPE_UINT8 || row >= rows_read_)
96    return false;
97
98  *result = reinterpret_cast<uint8*>(data_.get() + kPmpHeaderSize)[row];
99  return true;
100}
101
102bool PmpColumnReader::ReadUInt64(const uint32 row, uint64* result) const {
103  DCHECK(data_.get() != NULL);
104
105  if (field_type_ != PMP_TYPE_UINT64 || row >= rows_read_)
106    return false;
107
108  *result = reinterpret_cast<uint64*>(data_.get() + kPmpHeaderSize)[row];
109  return true;
110}
111
112uint32 PmpColumnReader::rows_read() const {
113  DCHECK(data_.get() != NULL);
114  return rows_read_;
115}
116
117bool PmpColumnReader::ParseData(const PmpFieldType expected_type) {
118  DCHECK(data_.get() != NULL);
119  DCHECK_GE(length_, kPmpHeaderSize);
120
121  // Check all magic bytes.
122  if (memcmp(&kPmpMagic1, &data_[kPmpMagic1Offset], sizeof(kPmpMagic1)) != 0 ||
123      memcmp(&kPmpMagic2, &data_[kPmpMagic2Offset], sizeof(kPmpMagic2)) != 0 ||
124      memcmp(&kPmpMagic3, &data_[kPmpMagic3Offset], sizeof(kPmpMagic3)) != 0 ||
125      memcmp(&kPmpMagic4, &data_[kPmpMagic4Offset], sizeof(kPmpMagic4)) != 0) {
126    return false;
127  }
128
129  uint16 field_type_data =
130      *(reinterpret_cast<uint16*>(&data_[kPmpFieldType1Offset]));
131
132  // Verify if field type matches second declaration
133  if (field_type_data !=
134      *(reinterpret_cast<uint16*>(&data_[kPmpFieldType2Offset]))) {
135    return false;
136  }
137
138  field_type_ = static_cast<PmpFieldType>(field_type_data);
139
140  if (field_type_ != expected_type)
141    return false;
142
143  rows_read_ = *(reinterpret_cast<uint32*>(&data_[kPmpRowCountOffset]));
144
145  // Sanity check against malicious row field.
146  if (rows_read_ > (kPmpMaxFilesize - kPmpHeaderSize))
147    return false;
148
149  DCHECK_GE(length_, kPmpHeaderSize);
150  int64 body_length = length_ - kPmpHeaderSize;
151  int64 expected_body_length = 0;
152  switch (field_type_) {
153    case PMP_TYPE_STRING:
154      expected_body_length = IndexStrings();
155      break;
156    case PMP_TYPE_UINT32:
157      expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint32);
158      break;
159    case PMP_TYPE_DOUBLE64:
160      expected_body_length = static_cast<int64>(rows_read_) * sizeof(double);
161      break;
162    case PMP_TYPE_UINT8:
163      expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint8);
164      break;
165    case PMP_TYPE_UINT64:
166      expected_body_length = static_cast<int64>(rows_read_) * sizeof(uint64);
167      break;
168    default:
169      return false;
170      break;
171  }
172
173  return body_length == expected_body_length;
174}
175
176int64 PmpColumnReader::IndexStrings() {
177  DCHECK(data_.get() != NULL);
178  DCHECK_GE(length_, kPmpHeaderSize);
179
180  strings_.reserve(rows_read_);
181
182  int64 bytes_parsed = kPmpHeaderSize;
183  const uint8* data_cursor = data_.get() + kPmpHeaderSize;
184
185  while (strings_.size() < rows_read_) {
186    const uint8* string_end = static_cast<const uint8*>(
187        memchr(data_cursor, '\0', length_ - bytes_parsed));
188
189    // Fail if cannot find null termination. String runs on past file end.
190    if (string_end == NULL)
191      return -1;
192
193    // Length of string. (+1 to include the termination character).
194    ptrdiff_t length_in_bytes = string_end - data_cursor + 1;
195
196    strings_.push_back(reinterpret_cast<const char*>(data_cursor));
197    data_cursor += length_in_bytes;
198    bytes_parsed += length_in_bytes;
199  }
200
201  return bytes_parsed - kPmpHeaderSize;
202}
203
204}  // namespace picasa
205