1// Copyright (c) 2012 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 "ui/base/resource/data_pack.h"
6
7#include <errno.h>
8
9#include "base/file_util.h"
10#include "base/files/memory_mapped_file.h"
11#include "base/logging.h"
12#include "base/memory/ref_counted_memory.h"
13#include "base/metrics/histogram.h"
14#include "base/strings/string_piece.h"
15
16// For details of the file layout, see
17// http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
18
19namespace {
20
21static const uint32 kFileFormatVersion = 4;
22// Length of file header: version, entry count and text encoding type.
23static const size_t kHeaderLength = 2 * sizeof(uint32) + sizeof(uint8);
24
25#pragma pack(push,2)
26struct DataPackEntry {
27  uint16 resource_id;
28  uint32 file_offset;
29
30  static int CompareById(const void* void_key, const void* void_entry) {
31    uint16 key = *reinterpret_cast<const uint16*>(void_key);
32    const DataPackEntry* entry =
33        reinterpret_cast<const DataPackEntry*>(void_entry);
34    if (key < entry->resource_id) {
35      return -1;
36    } else if (key > entry->resource_id) {
37      return 1;
38    } else {
39      return 0;
40    }
41  }
42};
43#pragma pack(pop)
44
45COMPILE_ASSERT(sizeof(DataPackEntry) == 6, size_of_entry_must_be_six);
46
47// We're crashing when trying to load a pak file on Windows.  Add some error
48// codes for logging.
49// http://crbug.com/58056
50enum LoadErrors {
51  INIT_FAILED = 1,
52  BAD_VERSION,
53  INDEX_TRUNCATED,
54  ENTRY_NOT_FOUND,
55  HEADER_TRUNCATED,
56  WRONG_ENCODING,
57  INIT_FAILED_FROM_FILE,
58
59  LOAD_ERRORS_COUNT,
60};
61
62}  // namespace
63
64namespace ui {
65
66DataPack::DataPack(ui::ScaleFactor scale_factor)
67    : resource_count_(0),
68      text_encoding_type_(BINARY),
69      scale_factor_(scale_factor) {
70}
71
72DataPack::~DataPack() {
73}
74
75bool DataPack::LoadFromPath(const base::FilePath& path) {
76  mmap_.reset(new base::MemoryMappedFile);
77  if (!mmap_->Initialize(path)) {
78    DLOG(ERROR) << "Failed to mmap datapack";
79    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED,
80                              LOAD_ERRORS_COUNT);
81    mmap_.reset();
82    return false;
83  }
84  return LoadImpl();
85}
86
87bool DataPack::LoadFromFile(base::PlatformFile file) {
88  mmap_.reset(new base::MemoryMappedFile);
89  if (!mmap_->Initialize(file)) {
90    DLOG(ERROR) << "Failed to mmap datapack";
91    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED_FROM_FILE,
92                              LOAD_ERRORS_COUNT);
93    mmap_.reset();
94    return false;
95  }
96  return LoadImpl();
97}
98
99bool DataPack::LoadImpl() {
100  // Sanity check the header of the file.
101  if (kHeaderLength > mmap_->length()) {
102    DLOG(ERROR) << "Data pack file corruption: incomplete file header.";
103    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", HEADER_TRUNCATED,
104                              LOAD_ERRORS_COUNT);
105    mmap_.reset();
106    return false;
107  }
108
109  // Parse the header of the file.
110  // First uint32: version; second: resource count;
111  const uint32* ptr = reinterpret_cast<const uint32*>(mmap_->data());
112  uint32 version = ptr[0];
113  if (version != kFileFormatVersion) {
114    LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
115               << kFileFormatVersion;
116    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", BAD_VERSION,
117                              LOAD_ERRORS_COUNT);
118    mmap_.reset();
119    return false;
120  }
121  resource_count_ = ptr[1];
122
123  // third: text encoding.
124  const uint8* ptr_encoding = reinterpret_cast<const uint8*>(ptr + 2);
125  text_encoding_type_ = static_cast<TextEncodingType>(*ptr_encoding);
126  if (text_encoding_type_ != UTF8 && text_encoding_type_ != UTF16 &&
127      text_encoding_type_ != BINARY) {
128    LOG(ERROR) << "Bad data pack text encoding: got " << text_encoding_type_
129               << ", expected between " << BINARY << " and " << UTF16;
130    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", WRONG_ENCODING,
131                              LOAD_ERRORS_COUNT);
132    mmap_.reset();
133    return false;
134  }
135
136  // Sanity check the file.
137  // 1) Check we have enough entries.
138  if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) >
139      mmap_->length()) {
140    LOG(ERROR) << "Data pack file corruption: too short for number of "
141                  "entries specified.";
142    UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED,
143                              LOAD_ERRORS_COUNT);
144    mmap_.reset();
145    return false;
146  }
147  // 2) Verify the entries are within the appropriate bounds. There's an extra
148  // entry after the last item which gives us the length of the last item.
149  for (size_t i = 0; i < resource_count_ + 1; ++i) {
150    const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
151        mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
152    if (entry->file_offset > mmap_->length()) {
153      LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
154                 << "Was the file corrupted?";
155      UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND,
156                                LOAD_ERRORS_COUNT);
157      mmap_.reset();
158      return false;
159    }
160  }
161
162  return true;
163}
164
165bool DataPack::HasResource(uint16 resource_id) const {
166  return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
167                   sizeof(DataPackEntry), DataPackEntry::CompareById);
168}
169
170bool DataPack::GetStringPiece(uint16 resource_id,
171                              base::StringPiece* data) const {
172  // It won't be hard to make this endian-agnostic, but it's not worth
173  // bothering to do right now.
174#if defined(__BYTE_ORDER)
175  // Linux check
176  COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
177                 datapack_assumes_little_endian);
178#elif defined(__BIG_ENDIAN__)
179  // Mac check
180  #error DataPack assumes little endian
181#endif
182
183  const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>(
184      bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
185              sizeof(DataPackEntry), DataPackEntry::CompareById));
186  if (!target) {
187    return false;
188  }
189
190  const DataPackEntry* next_entry = target + 1;
191  size_t length = next_entry->file_offset - target->file_offset;
192
193  data->set(mmap_->data() + target->file_offset, length);
194  return true;
195}
196
197base::RefCountedStaticMemory* DataPack::GetStaticMemory(
198    uint16 resource_id) const {
199  base::StringPiece piece;
200  if (!GetStringPiece(resource_id, &piece))
201    return NULL;
202
203  return new base::RefCountedStaticMemory(
204      reinterpret_cast<const unsigned char*>(piece.data()), piece.length());
205}
206
207ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
208  return text_encoding_type_;
209}
210
211ui::ScaleFactor DataPack::GetScaleFactor() const {
212  return scale_factor_;
213}
214
215// static
216bool DataPack::WritePack(const base::FilePath& path,
217                         const std::map<uint16, base::StringPiece>& resources,
218                         TextEncodingType textEncodingType) {
219  FILE* file = base::OpenFile(path, "wb");
220  if (!file)
221    return false;
222
223  if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) {
224    LOG(ERROR) << "Failed to write file version";
225    base::CloseFile(file);
226    return false;
227  }
228
229  // Note: the python version of this function explicitly sorted keys, but
230  // std::map is a sorted associative container, we shouldn't have to do that.
231  uint32 entry_count = resources.size();
232  if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) {
233    LOG(ERROR) << "Failed to write entry count";
234    base::CloseFile(file);
235    return false;
236  }
237
238  if (textEncodingType != UTF8 && textEncodingType != UTF16 &&
239      textEncodingType != BINARY) {
240    LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType
241               << ", expected between " << BINARY << " and " << UTF16;
242    base::CloseFile(file);
243    return false;
244  }
245
246  uint8 write_buffer = textEncodingType;
247  if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) {
248    LOG(ERROR) << "Failed to write file text resources encoding";
249    base::CloseFile(file);
250    return false;
251  }
252
253  // Each entry is a uint16 + a uint32. We have an extra entry after the last
254  // item so we can compute the size of the list item.
255  uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry);
256  uint32 data_offset = kHeaderLength + index_length;
257  for (std::map<uint16, base::StringPiece>::const_iterator it =
258           resources.begin();
259       it != resources.end(); ++it) {
260    uint16 resource_id = it->first;
261    if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
262      LOG(ERROR) << "Failed to write id for " << resource_id;
263      base::CloseFile(file);
264      return false;
265    }
266
267    if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
268      LOG(ERROR) << "Failed to write offset for " << resource_id;
269      base::CloseFile(file);
270      return false;
271    }
272
273    data_offset += it->second.length();
274  }
275
276  // We place an extra entry after the last item that allows us to read the
277  // size of the last item.
278  uint16 resource_id = 0;
279  if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
280    LOG(ERROR) << "Failed to write extra resource id.";
281    base::CloseFile(file);
282    return false;
283  }
284
285  if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
286    LOG(ERROR) << "Failed to write extra offset.";
287    base::CloseFile(file);
288    return false;
289  }
290
291  for (std::map<uint16, base::StringPiece>::const_iterator it =
292           resources.begin();
293       it != resources.end(); ++it) {
294    if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) {
295      LOG(ERROR) << "Failed to write data for " << it->first;
296      base::CloseFile(file);
297      return false;
298    }
299  }
300
301  base::CloseFile(file);
302
303  return true;
304}
305
306}  // namespace ui
307