• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
30 namespace errors = extension_manifest_errors;
31 
32 namespace extension_file_util {
33 
34 // Validates locale info. Doesn't check if messages.json files are valid.
35 static 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.
39 static bool IsScriptValid(const FilePath& path, const FilePath& relative_path,
40                           int message_id, std::string* error);
41 
42 const char kInstallDirectoryName[] = "Extensions";
43 
InstallExtension(const FilePath & unpacked_source_dir,const std::string & id,const std::string & version,const FilePath & all_extensions_dir)44 FilePath 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 
UninstallExtension(const FilePath & extensions_dir,const std::string & id)81 void 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 
LoadExtension(const FilePath & extension_path,Extension::Location location,int flags,std::string * error)89 scoped_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 
ValidateExtension(Extension * extension,std::string * error)141 bool 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 
GarbageCollectExtensions(const FilePath & install_directory,const std::map<std::string,FilePath> & extension_paths)307 void 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 
LoadExtensionMessageBundle(const FilePath & extension_path,const std::string & default_locale,std::string * error)370 ExtensionMessageBundle* 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 
ValidateLocaleInfo(const Extension & extension,std::string * error)402 static 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 
IsScriptValid(const FilePath & path,const FilePath & relative_path,int message_id,std::string * error)460 static 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 
CheckForIllegalFilenames(const FilePath & extension_path,std::string * error)483 bool 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 
ExtensionURLToRelativeFilePath(const GURL & url)521 FilePath 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 
GetUserDataTempDir()552 FilePath 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 
DeleteFile(const FilePath & path,bool recursive)618 void DeleteFile(const FilePath& path, bool recursive) {
619   file_util::Delete(path, recursive);
620 }
621 
622 }  // namespace extension_file_util
623