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