8e9ee30eef60b64714ba186750ddf50b301d3336
[platform/framework/web/crosswalk.git] / src / ui / base / resource / data_pack.cc
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
19 namespace {
20
21 static const uint32 kFileFormatVersion = 4;
22 // Length of file header: version, entry count and text encoding type.
23 static const size_t kHeaderLength = 2 * sizeof(uint32) + sizeof(uint8);
24
25 #pragma pack(push,2)
26 struct 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
45 COMPILE_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
50 enum 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
64 namespace ui {
65
66 DataPack::DataPack(ui::ScaleFactor scale_factor)
67     : resource_count_(0),
68       text_encoding_type_(BINARY),
69       scale_factor_(scale_factor) {
70 }
71
72 DataPack::~DataPack() {
73 }
74
75 bool 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
87 bool DataPack::LoadFromFile(base::File file) {
88   mmap_.reset(new base::MemoryMappedFile);
89   if (!mmap_->Initialize(file.Pass())) {
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
99 bool 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. There's an extra entry after the last item
138   // which gives the length of the last item.
139   if (kHeaderLength + (resource_count_ + 1) * sizeof(DataPackEntry) >
140       mmap_->length()) {
141     LOG(ERROR) << "Data pack file corruption: too short for number of "
142                   "entries specified.";
143     UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED,
144                               LOAD_ERRORS_COUNT);
145     mmap_.reset();
146     return false;
147   }
148   // 2) Verify the entries are within the appropriate bounds. There's an extra
149   // entry after the last item which gives us the length of the last item.
150   for (size_t i = 0; i < resource_count_ + 1; ++i) {
151     const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
152         mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
153     if (entry->file_offset > mmap_->length()) {
154       LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
155                  << "Was the file corrupted?";
156       UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND,
157                                 LOAD_ERRORS_COUNT);
158       mmap_.reset();
159       return false;
160     }
161   }
162
163   return true;
164 }
165
166 bool DataPack::HasResource(uint16 resource_id) const {
167   return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
168                    sizeof(DataPackEntry), DataPackEntry::CompareById);
169 }
170
171 bool DataPack::GetStringPiece(uint16 resource_id,
172                               base::StringPiece* data) const {
173   // It won't be hard to make this endian-agnostic, but it's not worth
174   // bothering to do right now.
175 #if defined(__BYTE_ORDER)
176   // Linux check
177   COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
178                  datapack_assumes_little_endian);
179 #elif defined(__BIG_ENDIAN__)
180   // Mac check
181   #error DataPack assumes little endian
182 #endif
183
184   const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>(
185       bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
186               sizeof(DataPackEntry), DataPackEntry::CompareById));
187   if (!target) {
188     return false;
189   }
190
191   const DataPackEntry* next_entry = target + 1;
192   // If the next entry points beyond the end of the file this data pack's entry
193   // table is corrupt. Log an error and return false. See
194   // http://crbug.com/371301.
195   if (next_entry->file_offset > mmap_->length()) {
196     size_t entry_index = target -
197         reinterpret_cast<const DataPackEntry*>(mmap_->data() + kHeaderLength);
198     LOG(ERROR) << "Entry #" << entry_index << " in data pack points off end "
199                << "of file. This should have been caught when loading. Was the "
200                << "file modified?";
201     return false;
202   }
203
204   size_t length = next_entry->file_offset - target->file_offset;
205   data->set(reinterpret_cast<const char*>(mmap_->data() + target->file_offset),
206             length);
207   return true;
208 }
209
210 base::RefCountedStaticMemory* DataPack::GetStaticMemory(
211     uint16 resource_id) const {
212   base::StringPiece piece;
213   if (!GetStringPiece(resource_id, &piece))
214     return NULL;
215
216   return new base::RefCountedStaticMemory(piece.data(), piece.length());
217 }
218
219 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
220   return text_encoding_type_;
221 }
222
223 ui::ScaleFactor DataPack::GetScaleFactor() const {
224   return scale_factor_;
225 }
226
227 // static
228 bool DataPack::WritePack(const base::FilePath& path,
229                          const std::map<uint16, base::StringPiece>& resources,
230                          TextEncodingType textEncodingType) {
231   FILE* file = base::OpenFile(path, "wb");
232   if (!file)
233     return false;
234
235   if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) {
236     LOG(ERROR) << "Failed to write file version";
237     base::CloseFile(file);
238     return false;
239   }
240
241   // Note: the python version of this function explicitly sorted keys, but
242   // std::map is a sorted associative container, we shouldn't have to do that.
243   uint32 entry_count = resources.size();
244   if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) {
245     LOG(ERROR) << "Failed to write entry count";
246     base::CloseFile(file);
247     return false;
248   }
249
250   if (textEncodingType != UTF8 && textEncodingType != UTF16 &&
251       textEncodingType != BINARY) {
252     LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType
253                << ", expected between " << BINARY << " and " << UTF16;
254     base::CloseFile(file);
255     return false;
256   }
257
258   uint8 write_buffer = textEncodingType;
259   if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) {
260     LOG(ERROR) << "Failed to write file text resources encoding";
261     base::CloseFile(file);
262     return false;
263   }
264
265   // Each entry is a uint16 + a uint32. We have an extra entry after the last
266   // item so we can compute the size of the list item.
267   uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry);
268   uint32 data_offset = kHeaderLength + index_length;
269   for (std::map<uint16, base::StringPiece>::const_iterator it =
270            resources.begin();
271        it != resources.end(); ++it) {
272     uint16 resource_id = it->first;
273     if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
274       LOG(ERROR) << "Failed to write id for " << resource_id;
275       base::CloseFile(file);
276       return false;
277     }
278
279     if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
280       LOG(ERROR) << "Failed to write offset for " << resource_id;
281       base::CloseFile(file);
282       return false;
283     }
284
285     data_offset += it->second.length();
286   }
287
288   // We place an extra entry after the last item that allows us to read the
289   // size of the last item.
290   uint16 resource_id = 0;
291   if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
292     LOG(ERROR) << "Failed to write extra resource id.";
293     base::CloseFile(file);
294     return false;
295   }
296
297   if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
298     LOG(ERROR) << "Failed to write extra offset.";
299     base::CloseFile(file);
300     return false;
301   }
302
303   for (std::map<uint16, base::StringPiece>::const_iterator it =
304            resources.begin();
305        it != resources.end(); ++it) {
306     if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) {
307       LOG(ERROR) << "Failed to write data for " << it->first;
308       base::CloseFile(file);
309       return false;
310     }
311   }
312
313   base::CloseFile(file);
314
315   return true;
316 }
317
318 }  // namespace ui