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_file_util.h"
6
7#include <map>
8#include <vector>
9
10#include "base/file_util.h"
11#include "base/logging.h"
12#include "base/memory/scoped_temp_dir.h"
13#include "base/metrics/histogram.h"
14#include "base/path_service.h"
15#include "base/threading/thread_restrictions.h"
16#include "base/utf_string_conversions.h"
17#include "chrome/common/chrome_paths.h"
18#include "chrome/common/extensions/extension.h"
19#include "chrome/common/extensions/extension_action.h"
20#include "chrome/common/extensions/extension_l10n_util.h"
21#include "chrome/common/extensions/extension_constants.h"
22#include "chrome/common/extensions/extension_resource.h"
23#include "chrome/common/extensions/extension_sidebar_defaults.h"
24#include "content/common/json_value_serializer.h"
25#include "grit/generated_resources.h"
26#include "net/base/escape.h"
27#include "net/base/file_stream.h"
28#include "ui/base/l10n/l10n_util.h"
29
30namespace errors = extension_manifest_errors;
31
32namespace extension_file_util {
33
34// Validates locale info. Doesn't check if messages.json files are valid.
35static bool ValidateLocaleInfo(const Extension& extension, std::string* error);
36
37// Returns false and sets the error if script file can't be loaded,
38// or if it's not UTF-8 encoded.
39static bool IsScriptValid(const FilePath& path, const FilePath& relative_path,
40                          int message_id, std::string* error);
41
42const char kInstallDirectoryName[] = "Extensions";
43
44FilePath InstallExtension(const FilePath& unpacked_source_dir,
45                          const std::string& id,
46                          const std::string& version,
47                          const FilePath& all_extensions_dir) {
48  FilePath extension_dir = all_extensions_dir.AppendASCII(id);
49  FilePath version_dir;
50
51  // Create the extension directory if it doesn't exist already.
52  if (!file_util::PathExists(extension_dir)) {
53    if (!file_util::CreateDirectory(extension_dir))
54      return FilePath();
55  }
56
57  // Try to find a free directory. There can be legitimate conflicts in the case
58  // of overinstallation of the same version.
59  const int kMaxAttempts = 100;
60  for (int i = 0; i < kMaxAttempts; ++i) {
61    FilePath candidate = extension_dir.AppendASCII(
62        base::StringPrintf("%s_%u", version.c_str(), i));
63    if (!file_util::PathExists(candidate)) {
64      version_dir = candidate;
65      break;
66    }
67  }
68
69  if (version_dir.empty()) {
70    LOG(ERROR) << "Could not find a home for extension " << id << " with "
71               << "version " << version << ".";
72    return FilePath();
73  }
74
75  if (!file_util::Move(unpacked_source_dir, version_dir))
76    return FilePath();
77
78  return version_dir;
79}
80
81void UninstallExtension(const FilePath& extensions_dir,
82                        const std::string& id) {
83  // We don't care about the return value. If this fails (and it can, due to
84  // plugins that aren't unloaded yet, it will get cleaned up by
85  // ExtensionService::GarbageCollectExtensions).
86  file_util::Delete(extensions_dir.AppendASCII(id), true);  // recursive.
87}
88
89scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
90                                       Extension::Location location,
91                                       int flags,
92                                       std::string* error) {
93  FilePath manifest_path =
94      extension_path.Append(Extension::kManifestFilename);
95  if (!file_util::PathExists(manifest_path)) {
96    *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
97    return NULL;
98  }
99
100  JSONFileValueSerializer serializer(manifest_path);
101  scoped_ptr<Value> root(serializer.Deserialize(NULL, error));
102  if (!root.get()) {
103    if (error->empty()) {
104      // If |error| is empty, than the file could not be read.
105      // It would be cleaner to have the JSON reader give a specific error
106      // in this case, but other code tests for a file error with
107      // error->empty().  For now, be consistent.
108      *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
109    } else {
110      *error = base::StringPrintf("%s  %s",
111                                  errors::kManifestParseError,
112                                  error->c_str());
113    }
114    return NULL;
115  }
116
117  if (!root->IsType(Value::TYPE_DICTIONARY)) {
118    *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
119    return NULL;
120  }
121
122  DictionaryValue* manifest = static_cast<DictionaryValue*>(root.get());
123  if (!extension_l10n_util::LocalizeExtension(extension_path, manifest, error))
124    return NULL;
125
126  scoped_refptr<Extension> extension(Extension::Create(
127      extension_path,
128      location,
129      *manifest,
130      flags,
131      error));
132  if (!extension.get())
133    return NULL;
134
135  if (!ValidateExtension(extension.get(), error))
136    return NULL;
137
138  return extension;
139}
140
141bool ValidateExtension(Extension* extension, std::string* error) {
142  // Validate icons exist.
143  for (ExtensionIconSet::IconMap::const_iterator iter =
144           extension->icons().map().begin();
145       iter != extension->icons().map().end();
146       ++iter) {
147    const FilePath path = extension->GetResource(iter->second).GetFilePath();
148    if (!file_util::PathExists(path)) {
149      *error =
150          l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ICON_FAILED,
151                                    UTF8ToUTF16(iter->second));
152      return false;
153    }
154  }
155
156  // Theme resource validation.
157  if (extension->is_theme()) {
158    DictionaryValue* images_value = extension->GetThemeImages();
159    if (images_value) {
160      for (DictionaryValue::key_iterator iter = images_value->begin_keys();
161           iter != images_value->end_keys(); ++iter) {
162        std::string val;
163        if (images_value->GetStringWithoutPathExpansion(*iter, &val)) {
164          FilePath image_path = extension->path().AppendASCII(val);
165          if (!file_util::PathExists(image_path)) {
166            *error =
167                l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH,
168                                          image_path.LossyDisplayName());
169            return false;
170          }
171        }
172      }
173    }
174
175    // Themes cannot contain other extension types.
176    return true;
177  }
178
179  // Validate that claimed script resources actually exist,
180  // and are UTF-8 encoded.
181  for (size_t i = 0; i < extension->content_scripts().size(); ++i) {
182    const UserScript& script = extension->content_scripts()[i];
183
184    for (size_t j = 0; j < script.js_scripts().size(); j++) {
185      const UserScript::File& js_script = script.js_scripts()[j];
186      const FilePath& path = ExtensionResource::GetFilePath(
187          js_script.extension_root(), js_script.relative_path());
188      if (!IsScriptValid(path, js_script.relative_path(),
189                         IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
190        return false;
191    }
192
193    for (size_t j = 0; j < script.css_scripts().size(); j++) {
194      const UserScript::File& css_script = script.css_scripts()[j];
195      const FilePath& path = ExtensionResource::GetFilePath(
196          css_script.extension_root(), css_script.relative_path());
197      if (!IsScriptValid(path, css_script.relative_path(),
198                         IDS_EXTENSION_LOAD_CSS_FAILED, error))
199        return false;
200    }
201  }
202
203  // Validate claimed plugin paths.
204  for (size_t i = 0; i < extension->plugins().size(); ++i) {
205    const Extension::PluginInfo& plugin = extension->plugins()[i];
206    if (!file_util::PathExists(plugin.path)) {
207      *error =
208          l10n_util::GetStringFUTF8(
209              IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
210              plugin.path.LossyDisplayName());
211      return false;
212    }
213  }
214
215  // Validate icon location for page actions.
216  ExtensionAction* page_action = extension->page_action();
217  if (page_action) {
218    std::vector<std::string> icon_paths(*page_action->icon_paths());
219    if (!page_action->default_icon_path().empty())
220      icon_paths.push_back(page_action->default_icon_path());
221    for (std::vector<std::string>::iterator iter = icon_paths.begin();
222         iter != icon_paths.end(); ++iter) {
223      if (!file_util::PathExists(extension->GetResource(*iter).GetFilePath())) {
224        *error =
225            l10n_util::GetStringFUTF8(
226                IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED,
227                UTF8ToUTF16(*iter));
228        return false;
229      }
230    }
231  }
232
233  // Validate icon location for browser actions.
234  // Note: browser actions don't use the icon_paths().
235  ExtensionAction* browser_action = extension->browser_action();
236  if (browser_action) {
237    std::string path = browser_action->default_icon_path();
238    if (!path.empty() &&
239        !file_util::PathExists(extension->GetResource(path).GetFilePath())) {
240        *error =
241            l10n_util::GetStringFUTF8(
242                IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED,
243                UTF8ToUTF16(path));
244        return false;
245    }
246  }
247
248  // Validate background page location, except for hosted apps, which should use
249  // an external URL. Background page for hosted apps are verified when the
250  // extension is created (in Extension::InitFromValue)
251  if (!extension->background_url().is_empty() && !extension->is_hosted_app()) {
252    FilePath page_path = ExtensionURLToRelativeFilePath(
253        extension->background_url());
254    const FilePath path = extension->GetResource(page_path).GetFilePath();
255    if (path.empty() || !file_util::PathExists(path)) {
256      *error =
257          l10n_util::GetStringFUTF8(
258              IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED,
259              page_path.LossyDisplayName());
260      return false;
261    }
262  }
263
264  // Validate path to the options page.  Don't check the URL for hosted apps,
265  // because they are expected to refer to an external URL.
266  if (!extension->options_url().is_empty() && !extension->is_hosted_app()) {
267    const FilePath options_path = ExtensionURLToRelativeFilePath(
268        extension->options_url());
269    const FilePath path = extension->GetResource(options_path).GetFilePath();
270    if (path.empty() || !file_util::PathExists(path)) {
271      *error =
272          l10n_util::GetStringFUTF8(
273              IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
274              options_path.LossyDisplayName());
275      return false;
276    }
277  }
278
279  // Validate sidebar default page location.
280  ExtensionSidebarDefaults* sidebar_defaults = extension->sidebar_defaults();
281  if (sidebar_defaults && sidebar_defaults->default_page().is_valid()) {
282    FilePath page_path = ExtensionURLToRelativeFilePath(
283        sidebar_defaults->default_page());
284    const FilePath path = extension->GetResource(page_path).GetFilePath();
285    if (path.empty() || !file_util::PathExists(path)) {
286      *error =
287          l10n_util::GetStringFUTF8(
288              IDS_EXTENSION_LOAD_SIDEBAR_PAGE_FAILED,
289              page_path.LossyDisplayName());
290      return false;
291    }
292  }
293
294  // Validate locale info.
295  if (!ValidateLocaleInfo(*extension, error))
296    return false;
297
298  // Check children of extension root to see if any of them start with _ and is
299  // not on the reserved list.
300  if (!CheckForIllegalFilenames(extension->path(), error)) {
301    return false;
302  }
303
304  return true;
305}
306
307void GarbageCollectExtensions(
308    const FilePath& install_directory,
309    const std::map<std::string, FilePath>& extension_paths) {
310  // Nothing to clean up if it doesn't exist.
311  if (!file_util::DirectoryExists(install_directory))
312    return;
313
314  VLOG(1) << "Garbage collecting extensions...";
315  file_util::FileEnumerator enumerator(install_directory,
316                                       false,  // Not recursive.
317                                       file_util::FileEnumerator::DIRECTORIES);
318  FilePath extension_path;
319  for (extension_path = enumerator.Next(); !extension_path.value().empty();
320       extension_path = enumerator.Next()) {
321    std::string extension_id;
322
323    FilePath basename = extension_path.BaseName();
324    if (IsStringASCII(basename.value())) {
325      extension_id = UTF16ToASCII(basename.LossyDisplayName());
326      if (!Extension::IdIsValid(extension_id))
327        extension_id.clear();
328    }
329
330    // Delete directories that aren't valid IDs.
331    if (extension_id.empty()) {
332      LOG(WARNING) << "Invalid extension ID encountered in extensions "
333                      "directory: " << basename.value();
334      VLOG(1) << "Deleting invalid extension directory "
335              << extension_path.value() << ".";
336      file_util::Delete(extension_path, true);  // Recursive.
337      continue;
338    }
339
340    std::map<std::string, FilePath>::const_iterator iter =
341        extension_paths.find(extension_id);
342
343    // If there is no entry in the prefs file, just delete the directory and
344    // move on. This can legitimately happen when an uninstall does not
345    // complete, for example, when a plugin is in use at uninstall time.
346    if (iter == extension_paths.end()) {
347      VLOG(1) << "Deleting unreferenced install for directory "
348              << extension_path.LossyDisplayName() << ".";
349      file_util::Delete(extension_path, true);  // Recursive.
350      continue;
351    }
352
353    // Clean up old version directories.
354    file_util::FileEnumerator versions_enumerator(
355        extension_path,
356        false,  // Not recursive.
357        file_util::FileEnumerator::DIRECTORIES);
358    for (FilePath version_dir = versions_enumerator.Next();
359         !version_dir.value().empty();
360         version_dir = versions_enumerator.Next()) {
361      if (version_dir.BaseName() != iter->second.BaseName()) {
362        VLOG(1) << "Deleting old version for directory "
363                << version_dir.LossyDisplayName() << ".";
364        file_util::Delete(version_dir, true);  // Recursive.
365      }
366    }
367  }
368}
369
370ExtensionMessageBundle* LoadExtensionMessageBundle(
371    const FilePath& extension_path,
372    const std::string& default_locale,
373    std::string* error) {
374  error->clear();
375  // Load locale information if available.
376  FilePath locale_path = extension_path.Append(Extension::kLocaleFolder);
377  if (!file_util::PathExists(locale_path))
378    return NULL;
379
380  std::set<std::string> locales;
381  if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error))
382    return NULL;
383
384  if (default_locale.empty() ||
385      locales.find(default_locale) == locales.end()) {
386    *error = l10n_util::GetStringUTF8(
387        IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
388    return NULL;
389  }
390
391  ExtensionMessageBundle* message_bundle =
392      extension_l10n_util::LoadMessageCatalogs(
393          locale_path,
394          default_locale,
395          extension_l10n_util::CurrentLocaleOrDefault(),
396          locales,
397          error);
398
399  return message_bundle;
400}
401
402static bool ValidateLocaleInfo(const Extension& extension, std::string* error) {
403  // default_locale and _locales have to be both present or both missing.
404  const FilePath path = extension.path().Append(Extension::kLocaleFolder);
405  bool path_exists = file_util::PathExists(path);
406  std::string default_locale = extension.default_locale();
407
408  // If both default locale and _locales folder are empty, skip verification.
409  if (default_locale.empty() && !path_exists)
410    return true;
411
412  if (default_locale.empty() && path_exists) {
413    *error = l10n_util::GetStringUTF8(
414        IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
415    return false;
416  } else if (!default_locale.empty() && !path_exists) {
417    *error = errors::kLocalesTreeMissing;
418    return false;
419  }
420
421  // Treat all folders under _locales as valid locales.
422  file_util::FileEnumerator locales(path,
423                                    false,
424                                    file_util::FileEnumerator::DIRECTORIES);
425
426  std::set<std::string> all_locales;
427  extension_l10n_util::GetAllLocales(&all_locales);
428  const FilePath default_locale_path = path.AppendASCII(default_locale);
429  bool has_default_locale_message_file = false;
430
431  FilePath locale_path;
432  while (!(locale_path = locales.Next()).empty()) {
433    if (extension_l10n_util::ShouldSkipValidation(path, locale_path,
434                                                  all_locales))
435      continue;
436
437    FilePath messages_path =
438        locale_path.Append(Extension::kMessagesFilename);
439
440    if (!file_util::PathExists(messages_path)) {
441      *error = base::StringPrintf(
442          "%s %s", errors::kLocalesMessagesFileMissing,
443          UTF16ToUTF8(messages_path.LossyDisplayName()).c_str());
444      return false;
445    }
446
447    if (locale_path == default_locale_path)
448      has_default_locale_message_file = true;
449  }
450
451  // Only message file for default locale has to exist.
452  if (!has_default_locale_message_file) {
453    *error = errors::kLocalesNoDefaultMessages;
454    return false;
455  }
456
457  return true;
458}
459
460static bool IsScriptValid(const FilePath& path,
461                          const FilePath& relative_path,
462                          int message_id,
463                          std::string* error) {
464  std::string content;
465  if (!file_util::PathExists(path) ||
466      !file_util::ReadFileToString(path, &content)) {
467    *error = l10n_util::GetStringFUTF8(
468        message_id,
469        relative_path.LossyDisplayName());
470    return false;
471  }
472
473  if (!IsStringUTF8(content)) {
474    *error = l10n_util::GetStringFUTF8(
475        IDS_EXTENSION_BAD_FILE_ENCODING,
476        relative_path.LossyDisplayName());
477    return false;
478  }
479
480  return true;
481}
482
483bool CheckForIllegalFilenames(const FilePath& extension_path,
484                              std::string* error) {
485  // Reserved underscore names.
486  static const FilePath::CharType* reserved_names[] = {
487    Extension::kLocaleFolder,
488    FILE_PATH_LITERAL("__MACOSX"),
489  };
490  static std::set<FilePath::StringType> reserved_underscore_names(
491      reserved_names, reserved_names + arraysize(reserved_names));
492
493  // Enumerate all files and directories in the extension root.
494  // There is a problem when using pattern "_*" with FileEnumerator, so we have
495  // to cheat with find_first_of and match all.
496  file_util::FileEnumerator all_files(
497    extension_path,
498    false,
499    static_cast<file_util::FileEnumerator::FILE_TYPE>(
500        file_util::FileEnumerator::DIRECTORIES |
501          file_util::FileEnumerator::FILES));
502
503  FilePath file;
504  while (!(file = all_files.Next()).empty()) {
505    FilePath::StringType filename = file.BaseName().value();
506    // Skip all that don't start with "_".
507    if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue;
508    if (reserved_underscore_names.find(filename) ==
509        reserved_underscore_names.end()) {
510      *error = base::StringPrintf(
511          "Cannot load extension with file or directory name %s. "
512          "Filenames starting with \"_\" are reserved for use by the system.",
513          filename.c_str());
514      return false;
515    }
516  }
517
518  return true;
519}
520
521FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
522  std::string url_path = url.path();
523  if (url_path.empty() || url_path[0] != '/')
524    return FilePath();
525
526  // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
527  std::string file_path = UnescapeURLComponent(url_path,
528      UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
529  size_t skip = file_path.find_first_not_of("/\\");
530  if (skip != file_path.npos)
531    file_path = file_path.substr(skip);
532
533  FilePath path =
534#if defined(OS_POSIX)
535    FilePath(file_path);
536#elif defined(OS_WIN)
537    FilePath(UTF8ToWide(file_path));
538#else
539    FilePath();
540    NOTIMPLEMENTED();
541#endif
542
543  // It's still possible for someone to construct an annoying URL whose path
544  // would still wind up not being considered relative at this point.
545  // For example: chrome-extension://id/c:////foo.html
546  if (path.IsAbsolute())
547    return FilePath();
548
549  return path;
550}
551
552FilePath GetUserDataTempDir() {
553  // We do file IO in this function, but only when the current profile's
554  // Temp directory has never been used before, or in a rare error case.
555  // Developers are not likely to see these situations often, so do an
556  // explicit thread check.
557  base::ThreadRestrictions::AssertIOAllowed();
558
559  // Getting chrome::DIR_USER_DATA_TEMP is failing.  Use histogram to see why.
560  // TODO(skerner): Fix the problem, and remove this code.  crbug.com/70056
561  enum DirectoryCreationResult {
562    SUCCESS = 0,
563
564    CANT_GET_PARENT_PATH,
565    CANT_GET_UDT_PATH,
566    NOT_A_DIRECTORY,
567    CANT_CREATE_DIR,
568    CANT_WRITE_TO_PATH,
569
570    UNSET,
571    NUM_DIRECTORY_CREATION_RESULTS
572  };
573
574  // All paths should set |result|.
575  DirectoryCreationResult result = UNSET;
576
577  FilePath temp_path;
578  if (!PathService::Get(chrome::DIR_USER_DATA_TEMP, &temp_path)) {
579    FilePath parent_path;
580    if (!PathService::Get(chrome::DIR_USER_DATA, &parent_path))
581      result = CANT_GET_PARENT_PATH;
582    else
583      result = CANT_GET_UDT_PATH;
584
585  } else if (file_util::PathExists(temp_path)) {
586
587    // Path exists.  Check that it is a directory we can write to.
588    if (!file_util::DirectoryExists(temp_path)) {
589      result = NOT_A_DIRECTORY;
590
591    } else if (!file_util::PathIsWritable(temp_path)) {
592      result = CANT_WRITE_TO_PATH;
593
594    } else {
595      // Temp is a writable directory.
596      result = SUCCESS;
597    }
598
599  } else if (!file_util::CreateDirectory(temp_path)) {
600    // Path doesn't exist, and we failed to create it.
601    result = CANT_CREATE_DIR;
602
603  } else {
604    // Successfully created the Temp directory.
605    result = SUCCESS;
606  }
607
608  UMA_HISTOGRAM_ENUMERATION("Extensions.GetUserDataTempDir",
609                            result,
610                            NUM_DIRECTORY_CREATION_RESULTS);
611
612  if (result == SUCCESS)
613    return temp_path;
614
615  return FilePath();
616}
617
618void DeleteFile(const FilePath& path, bool recursive) {
619  file_util::Delete(path, recursive);
620}
621
622}  // namespace extension_file_util
623