1// Copyright (c) 2011 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/common/extensions/extension_unpacker.h"
6
7#include <set>
8
9#include "base/file_util.h"
10#include "base/memory/scoped_handle.h"
11#include "base/memory/scoped_temp_dir.h"
12#include "base/string_util.h"
13#include "base/threading/thread.h"
14#include "base/utf_string_conversions.h"
15#include "base/values.h"
16#include "net/base/file_stream.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_constants.h"
19#include "chrome/common/extensions/extension_file_util.h"
20#include "chrome/common/extensions/extension_l10n_util.h"
21#include "chrome/common/url_constants.h"
22#include "chrome/common/zip.h"
23#include "content/common/common_param_traits.h"
24#include "content/common/json_value_serializer.h"
25#include "ipc/ipc_message_utils.h"
26#include "third_party/skia/include/core/SkBitmap.h"
27#include "webkit/glue/image_decoder.h"
28
29namespace errors = extension_manifest_errors;
30namespace keys = extension_manifest_keys;
31namespace filenames = extension_filenames;
32
33namespace {
34
35// Errors
36const char* kCouldNotCreateDirectoryError =
37    "Could not create directory for unzipping: ";
38const char* kCouldNotDecodeImageError = "Could not decode theme image.";
39const char* kCouldNotUnzipExtension = "Could not unzip extension.";
40const char* kPathNamesMustBeAbsoluteOrLocalError =
41    "Path names must not be absolute or contain '..'.";
42
43// A limit to stop us passing dangerously large canvases to the browser.
44const int kMaxImageCanvas = 4096 * 4096;
45
46SkBitmap DecodeImage(const FilePath& path) {
47  // Read the file from disk.
48  std::string file_contents;
49  if (!file_util::PathExists(path) ||
50      !file_util::ReadFileToString(path, &file_contents)) {
51    return SkBitmap();
52  }
53
54  // Decode the image using WebKit's image decoder.
55  const unsigned char* data =
56      reinterpret_cast<const unsigned char*>(file_contents.data());
57  webkit_glue::ImageDecoder decoder;
58  SkBitmap bitmap = decoder.Decode(data, file_contents.length());
59  Sk64 bitmap_size = bitmap.getSize64();
60  if (!bitmap_size.is32() || bitmap_size.get32() > kMaxImageCanvas)
61    return SkBitmap();
62  return bitmap;
63}
64
65bool PathContainsParentDirectory(const FilePath& path) {
66  const FilePath::StringType kSeparators(FilePath::kSeparators);
67  const FilePath::StringType kParentDirectory(FilePath::kParentDirectory);
68  const size_t npos = FilePath::StringType::npos;
69  const FilePath::StringType& value = path.value();
70
71  for (size_t i = 0; i < value.length(); ) {
72    i = value.find(kParentDirectory, i);
73    if (i != npos) {
74      if ((i == 0 || kSeparators.find(value[i-1]) == npos) &&
75          (i+1 < value.length() || kSeparators.find(value[i+1]) == npos)) {
76        return true;
77      }
78      ++i;
79    }
80  }
81
82  return false;
83}
84
85}  // namespace
86
87ExtensionUnpacker::ExtensionUnpacker(const FilePath& extension_path)
88    : extension_path_(extension_path) {
89}
90
91ExtensionUnpacker::~ExtensionUnpacker() {
92}
93
94DictionaryValue* ExtensionUnpacker::ReadManifest() {
95  FilePath manifest_path =
96      temp_install_dir_.Append(Extension::kManifestFilename);
97  if (!file_util::PathExists(manifest_path)) {
98    SetError(errors::kInvalidManifest);
99    return NULL;
100  }
101
102  JSONFileValueSerializer serializer(manifest_path);
103  std::string error;
104  scoped_ptr<Value> root(serializer.Deserialize(NULL, &error));
105  if (!root.get()) {
106    SetError(error);
107    return NULL;
108  }
109
110  if (!root->IsType(Value::TYPE_DICTIONARY)) {
111    SetError(errors::kInvalidManifest);
112    return NULL;
113  }
114
115  return static_cast<DictionaryValue*>(root.release());
116}
117
118bool ExtensionUnpacker::ReadAllMessageCatalogs(
119    const std::string& default_locale) {
120  FilePath locales_path =
121    temp_install_dir_.Append(Extension::kLocaleFolder);
122
123  // Not all folders under _locales have to be valid locales.
124  file_util::FileEnumerator locales(locales_path,
125                                    false,
126                                    file_util::FileEnumerator::DIRECTORIES);
127
128  std::set<std::string> all_locales;
129  extension_l10n_util::GetAllLocales(&all_locales);
130  FilePath locale_path;
131  while (!(locale_path = locales.Next()).empty()) {
132    if (extension_l10n_util::ShouldSkipValidation(locales_path, locale_path,
133                                                  all_locales))
134      continue;
135
136    FilePath messages_path =
137      locale_path.Append(Extension::kMessagesFilename);
138
139    if (!ReadMessageCatalog(messages_path))
140      return false;
141  }
142
143  return true;
144}
145
146bool ExtensionUnpacker::Run() {
147  VLOG(1) << "Installing extension " << extension_path_.value();
148
149  // <profile>/Extensions/INSTALL_TEMP/<version>
150  temp_install_dir_ =
151    extension_path_.DirName().AppendASCII(filenames::kTempExtensionName);
152
153  if (!file_util::CreateDirectory(temp_install_dir_)) {
154#if defined(OS_WIN)
155    std::string dir_string = WideToUTF8(temp_install_dir_.value());
156#else
157    std::string dir_string = temp_install_dir_.value();
158#endif
159
160    SetError(kCouldNotCreateDirectoryError + dir_string);
161    return false;
162  }
163
164  if (!Unzip(extension_path_, temp_install_dir_)) {
165    SetError(kCouldNotUnzipExtension);
166    return false;
167  }
168
169  // Parse the manifest.
170  parsed_manifest_.reset(ReadManifest());
171  if (!parsed_manifest_.get())
172    return false;  // Error was already reported.
173
174  // NOTE: Since the unpacker doesn't have the extension's public_id, the
175  // InitFromValue is allowed to generate a temporary id for the extension.
176  // ANY CODE THAT FOLLOWS SHOULD NOT DEPEND ON THE CORRECT ID OF THIS
177  // EXTENSION.
178  std::string error;
179  scoped_refptr<Extension> extension(Extension::Create(
180      temp_install_dir_,
181      Extension::INVALID,
182      *parsed_manifest_,
183      Extension::NO_FLAGS,
184      &error));
185  if (!extension.get()) {
186    SetError(error);
187    return false;
188  }
189
190  if (!extension_file_util::ValidateExtension(extension.get(), &error)) {
191    SetError(error);
192    return false;
193  }
194
195  // Decode any images that the browser needs to display.
196  std::set<FilePath> image_paths = extension->GetBrowserImages();
197  for (std::set<FilePath>::iterator it = image_paths.begin();
198       it != image_paths.end(); ++it) {
199    if (!AddDecodedImage(*it))
200      return false;  // Error was already reported.
201  }
202
203  // Parse all message catalogs (if any).
204  parsed_catalogs_.reset(new DictionaryValue);
205  if (!extension->default_locale().empty()) {
206    if (!ReadAllMessageCatalogs(extension->default_locale()))
207      return false;  // Error was already reported.
208  }
209
210  return true;
211}
212
213bool ExtensionUnpacker::DumpImagesToFile() {
214  IPC::Message pickle;  // We use a Message so we can use WriteParam.
215  IPC::WriteParam(&pickle, decoded_images_);
216
217  FilePath path = extension_path_.DirName().AppendASCII(
218      filenames::kDecodedImagesFilename);
219  if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
220                            pickle.size())) {
221    SetError("Could not write image data to disk.");
222    return false;
223  }
224
225  return true;
226}
227
228bool ExtensionUnpacker::DumpMessageCatalogsToFile() {
229  IPC::Message pickle;
230  IPC::WriteParam(&pickle, *parsed_catalogs_.get());
231
232  FilePath path = extension_path_.DirName().AppendASCII(
233      filenames::kDecodedMessageCatalogsFilename);
234  if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
235                            pickle.size())) {
236    SetError("Could not write message catalogs to disk.");
237    return false;
238  }
239
240  return true;
241}
242
243// static
244bool ExtensionUnpacker::ReadImagesFromFile(const FilePath& extension_path,
245                                           DecodedImages* images) {
246  FilePath path = extension_path.AppendASCII(filenames::kDecodedImagesFilename);
247  std::string file_str;
248  if (!file_util::ReadFileToString(path, &file_str))
249    return false;
250
251  IPC::Message pickle(file_str.data(), file_str.size());
252  void* iter = NULL;
253  return IPC::ReadParam(&pickle, &iter, images);
254}
255
256// static
257bool ExtensionUnpacker::ReadMessageCatalogsFromFile(
258    const FilePath& extension_path, DictionaryValue* catalogs) {
259  FilePath path = extension_path.AppendASCII(
260      filenames::kDecodedMessageCatalogsFilename);
261  std::string file_str;
262  if (!file_util::ReadFileToString(path, &file_str))
263    return false;
264
265  IPC::Message pickle(file_str.data(), file_str.size());
266  void* iter = NULL;
267  return IPC::ReadParam(&pickle, &iter, catalogs);
268}
269
270bool ExtensionUnpacker::AddDecodedImage(const FilePath& path) {
271  // Make sure it's not referencing a file outside the extension's subdir.
272  if (path.IsAbsolute() || PathContainsParentDirectory(path)) {
273    SetError(kPathNamesMustBeAbsoluteOrLocalError);
274    return false;
275  }
276
277  SkBitmap image_bitmap = DecodeImage(temp_install_dir_.Append(path));
278  if (image_bitmap.isNull()) {
279    SetError(kCouldNotDecodeImageError);
280    return false;
281  }
282
283  decoded_images_.push_back(MakeTuple(image_bitmap, path));
284  return true;
285}
286
287bool ExtensionUnpacker::ReadMessageCatalog(const FilePath& message_path) {
288  std::string error;
289  JSONFileValueSerializer serializer(message_path);
290  scoped_ptr<DictionaryValue> root(
291      static_cast<DictionaryValue*>(serializer.Deserialize(NULL, &error)));
292  if (!root.get()) {
293    string16 messages_file = message_path.LossyDisplayName();
294    if (error.empty()) {
295      // If file is missing, Deserialize will fail with empty error.
296      SetError(base::StringPrintf("%s %s", errors::kLocalesMessagesFileMissing,
297                                  UTF16ToUTF8(messages_file).c_str()));
298    } else {
299      SetError(base::StringPrintf("%s: %s",
300                                  UTF16ToUTF8(messages_file).c_str(),
301                                  error.c_str()));
302    }
303    return false;
304  }
305
306  FilePath relative_path;
307  // message_path was created from temp_install_dir. This should never fail.
308  if (!temp_install_dir_.AppendRelativePath(message_path, &relative_path)) {
309    NOTREACHED();
310    return false;
311  }
312
313  std::string dir_name = relative_path.DirName().MaybeAsASCII();
314  if (dir_name.empty()) {
315    NOTREACHED();
316    return false;
317  }
318  parsed_catalogs_->Set(dir_name, root.release());
319
320  return true;
321}
322
323void ExtensionUnpacker::SetError(const std::string &error) {
324  error_message_ = error;
325}
326