| // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extensions_service.h" |
| |
| #include "app/l10n_util.h" |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/gfx/png_encoder.h" |
| #include "base/scoped_handle.h" |
| #include "base/scoped_temp_dir.h" |
| #include "base/string_util.h" |
| #include "base/third_party/nss/blapi.h" |
| #include "base/third_party/nss/sha256.h" |
| #include "base/thread.h" |
| #include "base/values.h" |
| #include "net/base/file_stream.h" |
| #include "chrome/browser/browser.h" |
| #include "chrome/browser/browser_list.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_thread.h" |
| #include "chrome/browser/extensions/extension_browser_event_router.h" |
| #include "chrome/browser/extensions/extension_process_manager.h" |
| #include "chrome/browser/profile.h" |
| #include "chrome/browser/utility_process_host.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_error_reporter.h" |
| #include "chrome/common/extensions/extension_unpacker.h" |
| #include "chrome/common/json_value_serializer.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/pref_service.h" |
| #include "chrome/common/zip.h" |
| #include "chrome/common/url_constants.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| |
| #if defined(OS_WIN) |
| #include "app/win_util.h" |
| #include "base/registry.h" |
| #include "base/win_util.h" |
| #endif |
| |
| // ExtensionsService. |
| |
| const char* ExtensionsService::kInstallDirectoryName = "Extensions"; |
| const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; |
| const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL"; |
| |
| namespace { |
| // Chromium Extension magic number |
| const char kExtensionFileMagic[] = "Cr24"; |
| |
| struct ExtensionHeader { |
| char magic[sizeof(kExtensionFileMagic) - 1]; |
| uint32 version; |
| size_t header_size; |
| size_t manifest_size; |
| }; |
| |
| const size_t kZipHashBytes = 32; // SHA-256 |
| const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. |
| |
| // A preference that keeps track of extension settings. This is a dictionary |
| // object read from the Preferences file, keyed off of extension id's. |
| const wchar_t kExternalExtensionsPref[] = L"extensions.settings"; |
| |
| // A preference keeping track of how the extension was installed. |
| const wchar_t kLocation[] = L"location"; |
| const wchar_t kState[] = L"state"; |
| |
| // Registry key where registry defined extension installers live. |
| // TODO(port): Assuming this becomes a similar key into the appropriate |
| // platform system. |
| const char kRegistryExtensions[] = "Software\\Google\\Chrome\\Extensions"; |
| |
| #if defined(OS_WIN) |
| |
| // Registry value of of that key that defines the path to the .crx file. |
| const wchar_t kRegistryExtensionPath[] = L"path"; |
| |
| // Registry value of that key that defines the current version of the .crx file. |
| const wchar_t kRegistryExtensionVersion[] = L"version"; |
| |
| #endif |
| |
| // A marker file to indicate that an extension was installed from an external |
| // source. |
| const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; |
| |
| // A temporary subdirectory where we unpack extensions. |
| const char* kUnpackExtensionDir = "TEMP_UNPACK"; |
| |
| // The version of the extension package that this code understands. |
| const uint32 kExpectedVersion = 1; |
| } |
| |
| // This class coordinates an extension unpack task which is run in a separate |
| // process. Results are sent back to this class, which we route to the |
| // ExtensionServiceBackend. |
| class ExtensionsServiceBackend::UnpackerClient |
| : public UtilityProcessHost::Client { |
| public: |
| UnpackerClient(ExtensionsServiceBackend* backend, |
| const FilePath& extension_path, |
| const std::string& expected_id, |
| bool from_external) |
| : backend_(backend), extension_path_(extension_path), |
| expected_id_(expected_id), from_external_(from_external), |
| got_response_(false) { |
| } |
| |
| // Starts the unpack task. We call back to the backend when the task is done, |
| // or a problem occurs. |
| void Start() { |
| AddRef(); // balanced in OnUnpackExtensionReply() |
| |
| // TODO(mpcomplete): handle multiple installs |
| FilePath temp_dir = backend_->install_directory_.AppendASCII( |
| kUnpackExtensionDir); |
| if (!file_util::CreateDirectory(temp_dir)) { |
| backend_->ReportExtensionInstallError(extension_path_, |
| "Failed to create temporary directory."); |
| return; |
| } |
| |
| temp_extension_path_ = temp_dir.Append(extension_path_.BaseName()); |
| if (!file_util::CopyFile(extension_path_, temp_extension_path_)) { |
| backend_->ReportExtensionInstallError(extension_path_, |
| "Failed to copy extension file to temporary directory."); |
| return; |
| } |
| |
| if (backend_->resource_dispatcher_host_) { |
| ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread, |
| backend_->resource_dispatcher_host_, |
| MessageLoop::current())); |
| } else { |
| // Cheesy... but if we don't have a ResourceDispatcherHost, assume we're |
| // in a unit test and run the unpacker directly in-process. |
| ExtensionUnpacker unpacker(temp_extension_path_); |
| if (unpacker.Run()) { |
| OnUnpackExtensionSucceeded(*unpacker.parsed_manifest(), |
| unpacker.decoded_images()); |
| } else { |
| OnUnpackExtensionFailed(unpacker.error_message()); |
| } |
| } |
| } |
| |
| private: |
| // UtilityProcessHost::Client |
| virtual void OnProcessCrashed() { |
| // Don't report crashes if they happen after we got a response. |
| if (got_response_) |
| return; |
| |
| OnUnpackExtensionFailed("Chrome crashed while trying to install."); |
| } |
| |
| virtual void OnUnpackExtensionSucceeded( |
| const DictionaryValue& manifest, |
| const std::vector< Tuple2<SkBitmap, FilePath> >& images) { |
| // The extension was unpacked to the temp dir inside our unpacking dir. |
| FilePath extension_dir = temp_extension_path_.DirName().AppendASCII( |
| ExtensionsServiceBackend::kTempExtensionName); |
| backend_->OnExtensionUnpacked(extension_path_, extension_dir, |
| expected_id_, from_external_, |
| manifest, images); |
| Cleanup(); |
| } |
| |
| virtual void OnUnpackExtensionFailed(const std::string& error_message) { |
| backend_->ReportExtensionInstallError(extension_path_, error_message); |
| Cleanup(); |
| } |
| |
| // Cleans up our temp directory. |
| void Cleanup() { |
| if (got_response_) |
| return; |
| |
| got_response_ = true; |
| file_util::Delete(temp_extension_path_.DirName(), true); |
| Release(); // balanced in Run() |
| } |
| |
| // Starts the utility process that unpacks our extension. |
| void StartProcessOnIOThread(ResourceDispatcherHost* rdh, |
| MessageLoop* file_loop) { |
| UtilityProcessHost* host = new UtilityProcessHost(rdh, this, file_loop); |
| host->StartExtensionUnpacker(temp_extension_path_); |
| } |
| |
| scoped_refptr<ExtensionsServiceBackend> backend_; |
| |
| // The path to the crx file that we're installing. |
| FilePath extension_path_; |
| |
| // The path to the copy of the crx file in the temporary directory where we're |
| // unpacking it. |
| FilePath temp_extension_path_; |
| |
| // The ID we expect this extension to have, if any. |
| std::string expected_id_; |
| |
| // True if this is being installed from an external source. |
| bool from_external_; |
| |
| // True if we got a response from the utility process and have cleaned up |
| // already. |
| bool got_response_; |
| }; |
| |
| ExtensionsService::ExtensionsService(Profile* profile, |
| MessageLoop* frontend_loop, |
| MessageLoop* backend_loop, |
| const std::string& registry_path) |
| : prefs_(profile->GetPrefs()), |
| backend_loop_(backend_loop), |
| install_directory_(profile->GetPath().AppendASCII(kInstallDirectoryName)), |
| extensions_enabled_( |
| CommandLine::ForCurrentProcess()-> |
| HasSwitch(switches::kEnableExtensions)), |
| show_extensions_prompts_(true), |
| backend_(new ExtensionsServiceBackend( |
| install_directory_, g_browser_process->resource_dispatcher_host(), |
| frontend_loop, registry_path)) { |
| prefs_->RegisterDictionaryPref(kExternalExtensionsPref); |
| } |
| |
| ExtensionsService::~ExtensionsService() { |
| for (ExtensionList::iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| delete *iter; |
| } |
| } |
| |
| bool ExtensionsService::Init() { |
| // Start up the extension event routers. |
| ExtensionBrowserEventRouter::GetInstance()->Init(); |
| |
| scoped_ptr<DictionaryValue> external_extensions(new DictionaryValue); |
| GetExternalExtensions(external_extensions.get(), NULL); |
| |
| scoped_ptr< std::set<std::string> > |
| killed_extensions(new std::set<std::string>); |
| GetExternalExtensions(NULL, killed_extensions.get()); |
| |
| backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), |
| &ExtensionsServiceBackend::CheckForExternalUpdates, |
| *killed_extensions.get(), |
| external_extensions.get(), |
| scoped_refptr<ExtensionsService>(this))); |
| |
| backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), |
| &ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory, |
| scoped_refptr<ExtensionsService>(this), |
| external_extensions.release())); |
| |
| return true; |
| } |
| |
| void ExtensionsService::InstallExtension(const FilePath& extension_path) { |
| backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), |
| &ExtensionsServiceBackend::InstallExtension, |
| extension_path, |
| scoped_refptr<ExtensionsService>(this))); |
| } |
| |
| void ExtensionsService::UninstallExtension(const std::string& extension_id) { |
| Extension* extension = NULL; |
| |
| ExtensionList::iterator iter; |
| for (iter = extensions_.begin(); iter != extensions_.end(); ++iter) { |
| if ((*iter)->id() == extension_id) { |
| extension = *iter; |
| break; |
| } |
| } |
| |
| // Callers should not send us nonexistant extensions. |
| CHECK(extension); |
| |
| // Remove the extension from our list. |
| extensions_.erase(iter); |
| |
| // Tell other services the extension is gone. |
| NotificationService::current()->Notify(NotificationType::EXTENSION_UNLOADED, |
| NotificationService::AllSources(), |
| Details<Extension>(extension)); |
| |
| // For external extensions, we save a preference reminding ourself not to try |
| // and install the extension anymore. |
| if (Extension::IsExternalLocation(extension->location())) { |
| UpdateExtensionPref(ASCIIToWide(extension->id()), kState, |
| Value::CreateIntegerValue(Extension::KILLBIT), true); |
| } else { |
| UpdateExtensionPref(ASCIIToWide(extension->id()), kState, |
| Value::CreateIntegerValue(Extension::DISABLED), true); |
| } |
| |
| // Tell the backend to start deleting installed extensions on the file thread. |
| if (extension->location() == Extension::INTERNAL || |
| Extension::IsExternalLocation(extension->location())) { |
| backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), |
| &ExtensionsServiceBackend::UninstallExtension, extension_id)); |
| } |
| |
| delete extension; |
| } |
| |
| void ExtensionsService::LoadExtension(const FilePath& extension_path) { |
| backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), |
| &ExtensionsServiceBackend::LoadSingleExtension, |
| extension_path, scoped_refptr<ExtensionsService>(this))); |
| } |
| |
| void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) { |
| // Sync with manually loaded extensions. Otherwise we won't know about them |
| // since they aren't installed in the normal way. Eventually, we want to not |
| // load extensions at all from directory, but use the Extension preferences |
| // as the truth for what is installed. |
| DictionaryValue* pref = NULL; |
| for (ExtensionList::const_iterator iter = new_extensions->begin(); |
| iter != new_extensions->end(); ++iter) { |
| std::wstring extension_id = ASCIIToWide((*iter)->id()); |
| pref = GetOrCreateExtensionPref(extension_id); |
| Extension::Location location; |
| Extension::State state; |
| if (!pref->GetInteger(kLocation, reinterpret_cast<int*>(&location)) || |
| !pref->GetInteger(kState, reinterpret_cast<int*>(&state))) { |
| UpdateExtensionPref(extension_id, |
| kLocation, Value::CreateIntegerValue(Extension::INTERNAL), false); |
| UpdateExtensionPref(extension_id, |
| kState, Value::CreateIntegerValue(Extension::ENABLED), false); |
| } else { |
| // The kill-bit only applies to External extensions so this check fails |
| // for internal locations that have the kill-bit set. In other words, |
| // the kill-bit cannot be set unless the extension is external. |
| DCHECK(state != Extension::KILLBIT || |
| Extension::IsExternalLocation(location)); |
| } |
| } |
| |
| // If extensions aren't enabled, we still want to add themes. However, themes |
| // should not trigger EXTENSIONS_LOADED. |
| // TODO(aa): This can be re-enabled when BUG 13128 is fixed. |
| bool has_extension = false; |
| for (ExtensionList::iterator iter = new_extensions->begin(); |
| iter != new_extensions->end(); ++iter) { |
| if (extensions_enabled() || (*iter)->IsTheme()) { |
| extensions_.push_back(*iter); |
| if (!(*iter)->IsTheme()) |
| has_extension = true; |
| } |
| } |
| |
| if (has_extension) { |
| NotificationService::current()->Notify( |
| NotificationType::EXTENSIONS_LOADED, |
| NotificationService::AllSources(), |
| Details<ExtensionList>(new_extensions)); |
| } |
| delete new_extensions; |
| } |
| |
| void ExtensionsService::OnExtensionInstalled(Extension* extension, |
| bool update) { |
| UpdateExtensionPref(ASCIIToWide(extension->id()), kState, |
| Value::CreateIntegerValue(Extension::ENABLED), false); |
| UpdateExtensionPref(ASCIIToWide(extension->id()), kLocation, |
| Value::CreateIntegerValue(Extension::INTERNAL), true); |
| |
| // If the extension is a theme, tell the profile (and therefore ThemeProvider) |
| // to apply it. |
| if (extension->IsTheme()) { |
| NotificationService::current()->Notify( |
| NotificationType::THEME_INSTALLED, |
| NotificationService::AllSources(), |
| Details<Extension>(extension)); |
| } else { |
| NotificationService::current()->Notify( |
| NotificationType::EXTENSION_INSTALLED, |
| NotificationService::AllSources(), |
| Details<Extension>(extension)); |
| } |
| } |
| |
| void ExtensionsService::OnExternalExtensionInstalled( |
| const std::string& id, Extension::Location location) { |
| DCHECK(Extension::IsExternalLocation(location)); |
| UpdateExtensionPref(ASCIIToWide(id), kState, |
| Value::CreateIntegerValue(Extension::ENABLED), false); |
| UpdateExtensionPref(ASCIIToWide(id), kLocation, |
| Value::CreateIntegerValue(location), true); |
| } |
| |
| void ExtensionsService::OnExtensionVersionReinstalled(const std::string& id) { |
| Extension* extension = GetExtensionByID(id); |
| if (extension && extension->IsTheme()) { |
| NotificationService::current()->Notify( |
| NotificationType::THEME_INSTALLED, |
| NotificationService::AllSources(), |
| Details<Extension>(extension)); |
| } |
| } |
| |
| Extension* ExtensionsService::GetExtensionByID(std::string id) { |
| for (ExtensionList::const_iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| if ((*iter)->id() == id) |
| return *iter; |
| } |
| return NULL; |
| } |
| |
| void ExtensionsService::GetExternalExtensions( |
| DictionaryValue* external_extensions, |
| std::set<std::string>* killed_extensions) { |
| const DictionaryValue* dict = prefs_->GetDictionary(kExternalExtensionsPref); |
| if (!dict || dict->GetSize() == 0) |
| return; |
| |
| for (DictionaryValue::key_iterator i = dict->begin_keys(); |
| i != dict->end_keys(); ++i) { |
| std::wstring key_name = *i; |
| DCHECK(Extension::IdIsValid(WideToASCII(key_name))); |
| DictionaryValue* extension = NULL; |
| if (!dict->GetDictionary(key_name, &extension)) { |
| NOTREACHED(); |
| continue; |
| } |
| |
| // Check to see if the extension has been killed. |
| Extension::State state; |
| if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && |
| state == static_cast<int>(Extension::KILLBIT)) { |
| if (killed_extensions) { |
| StringToLowerASCII(&key_name); |
| killed_extensions->insert(WideToASCII(key_name)); |
| } |
| } |
| // Return all extensions found. |
| if (external_extensions) { |
| DictionaryValue* result = |
| static_cast<DictionaryValue*>(extension->DeepCopy()); |
| StringToLowerASCII(&key_name); |
| external_extensions->Set(key_name, result); |
| } |
| } |
| } |
| |
| DictionaryValue* ExtensionsService::GetOrCreateExtensionPref( |
| const std::wstring& extension_id) { |
| DictionaryValue* dict = prefs_->GetMutableDictionary(kExternalExtensionsPref); |
| DictionaryValue* extension = NULL; |
| if (!dict->GetDictionary(extension_id, &extension)) { |
| // Extension pref does not exist, create it. |
| extension = new DictionaryValue(); |
| dict->Set(extension_id, extension); |
| } |
| |
| return extension; |
| } |
| |
| bool ExtensionsService::UpdateExtensionPref(const std::wstring& extension_id, |
| const std::wstring& key, |
| Value* data_value, |
| bool schedule_save) { |
| DictionaryValue* extension = GetOrCreateExtensionPref(extension_id); |
| if (!extension->Set(key, data_value)) { |
| NOTREACHED() << L"Cannot modify key: '" << key.c_str() |
| << "' for extension: '" << extension_id.c_str() << "'"; |
| return false; |
| } |
| |
| if (schedule_save) |
| prefs_->ScheduleSavePersistentPrefs(); |
| return true; |
| } |
| |
| // ExtensionsServicesBackend |
| |
| ExtensionsServiceBackend::ExtensionsServiceBackend( |
| const FilePath& install_directory, ResourceDispatcherHost* rdh, |
| MessageLoop* frontend_loop, const std::string& registry_path) |
| : frontend_(NULL), |
| install_directory_(install_directory), |
| resource_dispatcher_host_(rdh), |
| alert_on_error_(false), |
| frontend_loop_(frontend_loop), |
| registry_path_(registry_path) { |
| // Default the registry path if unspecified. |
| if (registry_path_.empty()) { |
| registry_path_ = kRegistryExtensions; |
| } |
| } |
| |
| void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory( |
| scoped_refptr<ExtensionsService> frontend, |
| DictionaryValue* extension_prefs) { |
| frontend_ = frontend; |
| alert_on_error_ = false; |
| scoped_ptr<DictionaryValue> external_extensions(extension_prefs); |
| |
| #if defined(OS_WIN) |
| // On POSIX, AbsolutePath() calls realpath() which returns NULL if |
| // it does not exist. Instead we absolute-ify after creation in |
| // case that is needed. |
| // TODO(port): does this really need to happen before |
| // CreateDirectory() on Windows? |
| if (!file_util::AbsolutePath(&install_directory_)) |
| NOTREACHED(); |
| #endif |
| |
| scoped_ptr<ExtensionList> extensions(new ExtensionList); |
| |
| // Create the <Profile>/Extensions directory if it doesn't exist. |
| if (!file_util::DirectoryExists(install_directory_)) { |
| file_util::CreateDirectory(install_directory_); |
| LOG(INFO) << "Created Extensions directory. No extensions to install."; |
| ReportExtensionsLoaded(extensions.release()); |
| return; |
| } |
| |
| #if !defined(OS_WIN) |
| if (!file_util::AbsolutePath(&install_directory_)) |
| NOTREACHED(); |
| #endif |
| |
| LOG(INFO) << "Loading installed extensions..."; |
| |
| // Find all child directories in the install directory and load their |
| // manifests. Post errors and results to the frontend. |
| file_util::FileEnumerator enumerator(install_directory_, |
| false, // not recursive |
| file_util::FileEnumerator::DIRECTORIES); |
| FilePath extension_path; |
| for (extension_path = enumerator.Next(); !extension_path.value().empty(); |
| extension_path = enumerator.Next()) { |
| std::string extension_id = WideToASCII( |
| extension_path.BaseName().ToWStringHack()); |
| // The utility process might be in the middle of unpacking an extension, so |
| // ignore the temp unpacking directory. |
| if (extension_id == kUnpackExtensionDir) |
| continue; |
| |
| // If there is no Current Version file, just delete the directory and move |
| // on. This can legitimately happen when an uninstall does not complete, for |
| // example, when a plugin is in use at uninstall time. |
| FilePath current_version_path = extension_path.AppendASCII( |
| ExtensionsService::kCurrentVersionFileName); |
| if (!file_util::PathExists(current_version_path)) { |
| LOG(INFO) << "Deleting incomplete install for directory " |
| << WideToASCII(extension_path.ToWStringHack()) << "."; |
| file_util::Delete(extension_path, true); // Recursive. |
| continue; |
| } |
| |
| std::string current_version; |
| if (!ReadCurrentVersion(extension_path, ¤t_version)) |
| continue; |
| |
| Extension::Location location; |
| DictionaryValue* pref = NULL; |
| external_extensions->GetDictionary(ASCIIToWide(extension_id), &pref); |
| if (!pref || |
| !pref->GetInteger(kLocation, reinterpret_cast<int*>(&location))) { |
| location = Extension::INTERNAL; |
| } |
| FilePath version_path = extension_path.AppendASCII(current_version); |
| if (Extension::IsExternalLocation(location) && |
| CheckExternalUninstall(external_extensions.get(), |
| version_path, extension_id)) { |
| // TODO(erikkay): Possibly defer this operation to avoid slowing initial |
| // load of extensions. |
| UninstallExtension(extension_id); |
| |
| // No error needs to be reported. The extension effectively doesn't |
| // exist. |
| continue; |
| } |
| |
| Extension* extension = LoadExtension(version_path, true); // require id |
| if (extension) |
| extensions->push_back(extension); |
| } |
| |
| LOG(INFO) << "Done."; |
| ReportExtensionsLoaded(extensions.release()); |
| } |
| |
| void ExtensionsServiceBackend::LoadSingleExtension( |
| const FilePath& path_in, scoped_refptr<ExtensionsService> frontend) { |
| frontend_ = frontend; |
| |
| // Explicit UI loads are always noisy. |
| alert_on_error_ = true; |
| |
| FilePath extension_path = path_in; |
| if (!file_util::AbsolutePath(&extension_path)) |
| NOTREACHED(); |
| |
| LOG(INFO) << "Loading single extension from " << |
| WideToASCII(extension_path.BaseName().ToWStringHack()); |
| |
| Extension* extension = LoadExtension(extension_path, |
| false); // don't require ID |
| if (extension) { |
| extension->set_location(Extension::LOAD); |
| ExtensionList* extensions = new ExtensionList; |
| extensions->push_back(extension); |
| ReportExtensionsLoaded(extensions); |
| } |
| } |
| |
| Extension* ExtensionsServiceBackend::LoadExtension( |
| const FilePath& extension_path, bool require_id) { |
| FilePath manifest_path = |
| extension_path.AppendASCII(Extension::kManifestFilename); |
| if (!file_util::PathExists(manifest_path)) { |
| ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError); |
| return NULL; |
| } |
| |
| JSONFileValueSerializer serializer(manifest_path); |
| std::string error; |
| scoped_ptr<Value> root(serializer.Deserialize(&error)); |
| if (!root.get()) { |
| ReportExtensionLoadError(extension_path, error); |
| return NULL; |
| } |
| |
| if (!root->IsType(Value::TYPE_DICTIONARY)) { |
| ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError); |
| return NULL; |
| } |
| |
| scoped_ptr<Extension> extension(new Extension(extension_path)); |
| if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()), |
| require_id, &error)) { |
| ReportExtensionLoadError(extension_path, error); |
| return NULL; |
| } |
| |
| FilePath external_marker = extension_path.AppendASCII(kExternalInstallFile); |
| if (file_util::PathExists(external_marker)) { |
| extension->set_location( |
| extension->ExternalExtensionInstallType(registry_path_)); |
| } else { |
| extension->set_location(Extension::INTERNAL); |
| } |
| |
| // Theme resource validation. |
| if (extension->IsTheme()) { |
| DictionaryValue* images_value = extension->GetThemeImages(); |
| DictionaryValue::key_iterator iter = images_value->begin_keys(); |
| while (iter != images_value->end_keys()) { |
| std::string val; |
| if (images_value->GetString(*iter , &val)) { |
| FilePath image_path = extension->path().AppendASCII(val); |
| if (!file_util::PathExists(image_path)) { |
| ReportExtensionLoadError(extension_path, |
| StringPrintf("Could not load '%s' for theme.", |
| WideToUTF8(image_path.ToWStringHack()).c_str())); |
| return NULL; |
| } |
| } |
| ++iter; |
| } |
| |
| // Themes cannot contain other extension types. |
| return extension.release(); |
| } |
| |
| // Validate that claimed script resources actually exist. |
| for (size_t i = 0; i < extension->content_scripts().size(); ++i) { |
| const UserScript& script = extension->content_scripts()[i]; |
| |
| for (size_t j = 0; j < script.js_scripts().size(); j++) { |
| const FilePath& path = script.js_scripts()[j].path(); |
| if (!file_util::PathExists(path)) { |
| ReportExtensionLoadError(extension_path, |
| StringPrintf("Could not load '%s' for content script.", |
| WideToUTF8(path.ToWStringHack()).c_str())); |
| return NULL; |
| } |
| } |
| |
| for (size_t j = 0; j < script.css_scripts().size(); j++) { |
| const FilePath& path = script.css_scripts()[j].path(); |
| if (!file_util::PathExists(path)) { |
| ReportExtensionLoadError(extension_path, |
| StringPrintf("Could not load '%s' for content script.", |
| WideToUTF8(path.ToWStringHack()).c_str())); |
| return NULL; |
| } |
| } |
| } |
| |
| for (size_t i = 0; i < extension->plugins().size(); ++i) { |
| const Extension::PluginInfo& plugin = extension->plugins()[i]; |
| if (!file_util::PathExists(plugin.path)) { |
| ReportExtensionLoadError(extension_path, |
| StringPrintf("Could not load '%s' for plugin.", |
| WideToUTF8(plugin.path.ToWStringHack()).c_str())); |
| return NULL; |
| } |
| } |
| |
| // Validate icon location for page actions. |
| const PageActionMap& page_actions = extension->page_actions(); |
| for (PageActionMap::const_iterator i(page_actions.begin()); |
| i != page_actions.end(); ++i) { |
| PageAction* page_action = i->second; |
| FilePath path = page_action->icon_path(); |
| if (!file_util::PathExists(path)) { |
| ReportExtensionLoadError(extension_path, |
| StringPrintf("Could not load icon '%s' for page action.", |
| WideToUTF8(path.ToWStringHack()).c_str())); |
| return NULL; |
| } |
| } |
| |
| return extension.release(); |
| } |
| |
| void ExtensionsServiceBackend::ReportExtensionLoadError( |
| const FilePath& extension_path, const std::string &error) { |
| // TODO(port): note that this isn't guaranteed to work properly on Linux. |
| std::string path_str = WideToASCII(extension_path.ToWStringHack()); |
| std::string message = StringPrintf("Could not load extension from '%s'. %s", |
| path_str.c_str(), error.c_str()); |
| ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_); |
| } |
| |
| void ExtensionsServiceBackend::ReportExtensionsLoaded( |
| ExtensionList* extensions) { |
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( |
| frontend_, &ExtensionsService::OnExtensionsLoaded, extensions)); |
| } |
| |
| bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir, |
| std::string* version_string) { |
| FilePath current_version = |
| dir.AppendASCII(ExtensionsService::kCurrentVersionFileName); |
| if (file_util::PathExists(current_version)) { |
| if (file_util::ReadFileToString(current_version, version_string)) { |
| TrimWhitespace(*version_string, TRIM_ALL, version_string); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool ExtensionsServiceBackend::CheckCurrentVersion( |
| const std::string& new_version_str, |
| const std::string& current_version_str, |
| const FilePath& dest_dir) { |
| scoped_ptr<Version> current_version( |
| Version::GetVersionFromString(current_version_str)); |
| scoped_ptr<Version> new_version( |
| Version::GetVersionFromString(new_version_str)); |
| if (current_version->CompareTo(*new_version) >= 0) { |
| // Verify that the directory actually exists. If it doesn't we'll return |
| // true so that the install code will repair the broken installation. |
| // TODO(erikkay): A further step would be to verify that the extension |
| // has actually loaded successfully. |
| FilePath version_dir = dest_dir.AppendASCII(current_version_str); |
| if (file_util::PathExists(version_dir)) { |
| std::string id = WideToASCII(dest_dir.BaseName().ToWStringHack()); |
| StringToLowerASCII(&id); |
| ReportExtensionVersionReinstalled(id); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, |
| const FilePath& dest_dir) { |
| if (file_util::PathExists(dest_dir)) { |
| // By the time we get here, it should be safe to assume that this directory |
| // is not currently in use (it's not the current active version). |
| if (!file_util::Delete(dest_dir, true)) { |
| ReportExtensionInstallError(source_dir, |
| "Can't delete existing version directory."); |
| return false; |
| } |
| } else { |
| FilePath parent = dest_dir.DirName(); |
| if (!file_util::DirectoryExists(parent)) { |
| if (!file_util::CreateDirectory(parent)) { |
| ReportExtensionInstallError(source_dir, |
| "Couldn't create extension directory."); |
| return false; |
| } |
| } |
| } |
| if (!file_util::Move(source_dir, dest_dir)) { |
| ReportExtensionInstallError(source_dir, |
| "Couldn't move temporary directory."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, |
| std::string version) { |
| // Write out the new CurrentVersion file. |
| // <profile>/Extension/<name>/CurrentVersion |
| FilePath current_version = |
| dest_dir.AppendASCII(ExtensionsService::kCurrentVersionFileName); |
| FilePath current_version_old = |
| current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old")); |
| if (file_util::PathExists(current_version_old)) { |
| if (!file_util::Delete(current_version_old, false)) { |
| ReportExtensionInstallError(dest_dir, |
| "Couldn't remove CurrentVersion_old file."); |
| return false; |
| } |
| } |
| if (file_util::PathExists(current_version)) { |
| if (!file_util::Move(current_version, current_version_old)) { |
| ReportExtensionInstallError(dest_dir, |
| "Couldn't move CurrentVersion file."); |
| return false; |
| } |
| } |
| net::FileStream stream; |
| int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; |
| if (stream.Open(current_version, flags) != 0) |
| return false; |
| if (stream.Write(version.c_str(), version.size(), NULL) < 0) { |
| // Restore the old CurrentVersion. |
| if (file_util::PathExists(current_version_old)) { |
| if (!file_util::Move(current_version_old, current_version)) { |
| LOG(WARNING) << "couldn't restore " << current_version_old.value() << |
| " to " << current_version.value(); |
| |
| // TODO(erikkay): This is an ugly state to be in. Try harder? |
| } |
| } |
| ReportExtensionInstallError(dest_dir, |
| "Couldn't create CurrentVersion file."); |
| return false; |
| } |
| return true; |
| } |
| |
| void ExtensionsServiceBackend::InstallExtension( |
| const FilePath& extension_path, scoped_refptr<ExtensionsService> frontend) { |
| LOG(INFO) << "Installing extension " << extension_path.value(); |
| |
| frontend_ = frontend; |
| alert_on_error_ = true; |
| |
| bool from_external = false; |
| InstallOrUpdateExtension(extension_path, std::string(), from_external); |
| } |
| |
| void ExtensionsServiceBackend::InstallOrUpdateExtension( |
| const FilePath& extension_path, const std::string& expected_id, |
| bool from_external) { |
| UnpackerClient* client = |
| new UnpackerClient(this, extension_path, expected_id, from_external); |
| client->Start(); |
| } |
| |
| void ExtensionsServiceBackend::OnExtensionUnpacked( |
| const FilePath& extension_path, |
| const FilePath& temp_extension_dir, |
| const std::string expected_id, |
| bool from_external, |
| const DictionaryValue& manifest, |
| const std::vector< Tuple2<SkBitmap, FilePath> >& images) { |
| Extension extension; |
| std::string error; |
| if (!extension.InitFromValue(manifest, |
| true, // require ID |
| &error)) { |
| ReportExtensionInstallError(extension_path, "Invalid extension manifest."); |
| return; |
| } |
| |
| if (!frontend_->extensions_enabled() && !extension.IsTheme()) { |
| #if defined(OS_WIN) |
| if (frontend_->show_extensions_prompts()) { |
| win_util::MessageBox(GetActiveWindow(), |
| L"Extensions are not enabled. Add --enable-extensions to the " |
| L"command-line to enable extensions.\n\n" |
| L"This is a temporary message and it will be removed when extensions " |
| L"UI is finalized.", |
| l10n_util::GetString(IDS_PRODUCT_NAME).c_str(), MB_OK); |
| } |
| #endif |
| ReportExtensionInstallError(extension_path, |
| "Extensions are not enabled."); |
| return; |
| } |
| |
| #if defined(OS_WIN) |
| if (!extension.IsTheme() && |
| frontend_->show_extensions_prompts() && |
| win_util::MessageBox(GetActiveWindow(), |
| L"Are you sure you want to install this extension?\n\n" |
| L"This is a temporary message and it will be removed when extensions " |
| L"UI is finalized.", |
| l10n_util::GetString(IDS_PRODUCT_NAME).c_str(), |
| MB_OKCANCEL) == IDOK) { |
| ReportExtensionInstallError(extension_path, |
| "User did not allow extension to be installed."); |
| return; |
| } |
| #endif |
| |
| // If an expected id was provided, make sure it matches. |
| if (!expected_id.empty() && expected_id != extension.id()) { |
| std::string error_msg = "ID in new extension manifest ("; |
| error_msg += extension.id(); |
| error_msg += ") does not match expected ID ("; |
| error_msg += expected_id; |
| error_msg += ")"; |
| ReportExtensionInstallError(extension_path, error_msg); |
| return; |
| } |
| |
| // <profile>/Extensions/<id> |
| FilePath dest_dir = install_directory_.AppendASCII(extension.id()); |
| std::string version = extension.VersionString(); |
| std::string current_version; |
| bool was_update = false; |
| if (ReadCurrentVersion(dest_dir, ¤t_version)) { |
| if (!CheckCurrentVersion(version, current_version, dest_dir)) |
| return; |
| was_update = true; |
| } |
| |
| // Write our parsed manifest back to disk, to ensure it doesn't contain an |
| // exploitable bug that can be used to compromise the browser. |
| std::string manifest_json; |
| JSONStringValueSerializer serializer(&manifest_json); |
| serializer.set_pretty_print(true); |
| if (!serializer.Serialize(manifest)) { |
| ReportExtensionInstallError(extension_path, |
| "Error serializing manifest.json."); |
| return; |
| } |
| |
| FilePath manifest_path = |
| temp_extension_dir.AppendASCII(Extension::kManifestFilename); |
| if (!file_util::WriteFile(manifest_path, |
| manifest_json.data(), manifest_json.size())) { |
| ReportExtensionInstallError(extension_path, "Error saving manifest.json."); |
| return; |
| } |
| |
| // Write our parsed images back to disk as well. |
| for (size_t i = 0; i < images.size(); ++i) { |
| const SkBitmap& image = images[i].a; |
| FilePath path = temp_extension_dir.Append(images[i].b); |
| |
| std::vector<unsigned char> image_data; |
| // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even |
| // though they may originally be .jpg, etc. Figure something out. |
| // http://code.google.com/p/chromium/issues/detail?id=12459 |
| if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) { |
| ReportExtensionInstallError(extension_path, |
| "Error re-encoding theme image."); |
| return; |
| } |
| |
| // Note: we're overwriting existing files that the utility process wrote, |
| // so we can be sure the directory exists. |
| const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); |
| if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { |
| ReportExtensionInstallError(extension_path, "Error saving theme image."); |
| return; |
| } |
| } |
| |
| // <profile>/Extensions/<dir_name>/<version> |
| FilePath version_dir = dest_dir.AppendASCII(version); |
| |
| // If anything fails after this, we want to delete the extension dir. |
| ScopedTempDir scoped_version_dir; |
| scoped_version_dir.Set(version_dir); |
| |
| if (!InstallDirSafely(temp_extension_dir, version_dir)) |
| return; |
| |
| if (!SetCurrentVersion(dest_dir, version)) |
| return; |
| |
| // To mark that this extension was installed from an external source, create a |
| // zero-length file. At load time, this is used to indicate that the |
| // extension should be uninstalled. |
| // TODO(erikkay): move this into per-extension config storage when it appears. |
| if (from_external) { |
| FilePath marker = version_dir.AppendASCII(kExternalInstallFile); |
| file_util::WriteFile(marker, NULL, 0); |
| } |
| |
| // Load the extension immediately and then report installation success. We |
| // don't load extensions for external installs because external installation |
| // occurs before the normal startup so we just let startup pick them up. We |
| // notify on installation of external extensions because we need to update |
| // the preferences for these extensions to reflect that they've just been |
| // installed. |
| if (!from_external) { |
| Extension* extension = LoadExtension(version_dir, true); // require id |
| CHECK(extension); |
| |
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( |
| frontend_, &ExtensionsService::OnExtensionInstalled, extension, |
| was_update)); |
| |
| // Only one extension, but ReportExtensionsLoaded can handle multiple, |
| // so we need to construct a list. |
| scoped_ptr<ExtensionList> extensions(new ExtensionList); |
| extensions->push_back(extension); |
| LOG(INFO) << "Done."; |
| // Hand off ownership of the loaded extensions to the frontend. |
| ReportExtensionsLoaded(extensions.release()); |
| } else { |
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( |
| frontend_, &ExtensionsService::OnExternalExtensionInstalled, |
| extension.id(), |
| extension.ExternalExtensionInstallType(registry_path_))); |
| } |
| |
| scoped_version_dir.Take(); |
| } |
| |
| void ExtensionsServiceBackend::ReportExtensionInstallError( |
| const FilePath& extension_path, const std::string &error) { |
| |
| // TODO(erikkay): note that this isn't guaranteed to work properly on Linux. |
| std::string path_str = WideToASCII(extension_path.ToWStringHack()); |
| std::string message = |
| StringPrintf("Could not install extension from '%s'. %s", |
| path_str.c_str(), error.c_str()); |
| ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_); |
| } |
| |
| void ExtensionsServiceBackend::ReportExtensionVersionReinstalled( |
| const std::string& id) { |
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( |
| frontend_, &ExtensionsService::OnExtensionVersionReinstalled, id)); |
| } |
| |
| bool ExtensionsServiceBackend::ShouldSkipInstallingExtension( |
| const std::set<std::string>& ids_to_ignore, |
| const std::string& id) { |
| if (ids_to_ignore.find(id) != ids_to_ignore.end()) { |
| LOG(INFO) << "Skipping uninstalled external extension " << id; |
| return true; |
| } |
| return false; |
| } |
| |
| void ExtensionsServiceBackend::CheckVersionAndInstallExtension( |
| const std::string& id, const std::string& extension_version, |
| const FilePath& extension_path, bool from_external) { |
| if (ShouldInstall(id, extension_version)) |
| InstallOrUpdateExtension(FilePath(extension_path), id, from_external); |
| } |
| |
| // Some extensions will autoupdate themselves externally from Chrome. These |
| // are typically part of some larger client application package. To support |
| // these, the extension will register its location in the the preferences file |
| // (and also, on Windows, in the registry) and this code will periodically |
| // check that location for a .crx file, which it will then install locally if |
| // a new version is available. |
| void ExtensionsServiceBackend::CheckForExternalUpdates( |
| std::set<std::string> ids_to_ignore, |
| DictionaryValue* extension_prefs, |
| scoped_refptr<ExtensionsService> frontend) { |
| // Note that this installation is intentionally silent (since it didn't |
| // go through the front-end). Extensions that are registered in this |
| // way are effectively considered 'pre-bundled', and so implicitly |
| // trusted. In general, if something has HKLM or filesystem access, |
| // they could install an extension manually themselves anyway. |
| alert_on_error_ = false; |
| frontend_ = frontend; |
| |
| for (DictionaryValue::key_iterator i = extension_prefs->begin_keys(); |
| i != extension_prefs->end_keys(); ++i) { |
| const std::wstring& extension_id = *i; |
| if (ShouldSkipInstallingExtension(ids_to_ignore, WideToASCII(extension_id))) |
| continue; |
| |
| DictionaryValue* extension = NULL; |
| if (!extension_prefs->GetDictionary(extension_id, &extension)) { |
| NOTREACHED() << "Cannot read extension " << extension_id.c_str() |
| << " from dictionary."; |
| continue; |
| } |
| |
| Extension::Location location; |
| if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) && |
| location != Extension::EXTERNAL_PREF) { |
| continue; |
| } |
| Extension::State state; |
| if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && |
| state == Extension::KILLBIT) { |
| continue; |
| } |
| |
| FilePath::StringType external_crx; |
| std::string external_version; |
| if (!extension->GetString(L"external_crx", &external_crx) || |
| !extension->GetString(L"external_version", &external_version)) { |
| LOG(WARNING) << "Malformed extension dictionary for extension: " |
| << extension_id.c_str(); |
| continue; |
| } |
| |
| bool from_external = true; |
| CheckVersionAndInstallExtension(WideToASCII(extension_id), external_version, |
| FilePath(external_crx), from_external); |
| } |
| |
| #if defined(OS_WIN) |
| // TODO(port): Pull this out into an interface. That will also allow us to |
| // test the behavior of external extensions. |
| HKEY reg_root = HKEY_LOCAL_MACHINE; |
| RegistryKeyIterator iterator(reg_root, ASCIIToWide(registry_path_).c_str()); |
| while (iterator.Valid()) { |
| // Fold |
| std::string id = StringToLowerASCII(WideToASCII(iterator.Name())); |
| if (ShouldSkipInstallingExtension(ids_to_ignore, id)) { |
| ++iterator; |
| continue; |
| } |
| |
| RegKey key; |
| std::wstring key_path = ASCIIToWide(registry_path_); |
| key_path.append(L"\\"); |
| key_path.append(iterator.Name()); |
| if (key.Open(reg_root, key_path.c_str())) { |
| std::wstring extension_path; |
| if (key.ReadValue(kRegistryExtensionPath, &extension_path)) { |
| std::wstring extension_version; |
| if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) { |
| bool from_external = true; |
| CheckVersionAndInstallExtension( |
| id, WideToASCII(extension_version), FilePath(extension_path), |
| from_external); |
| } else { |
| // TODO(erikkay): find a way to get this into about:extensions |
| LOG(WARNING) << "Missing value " << kRegistryExtensionVersion << |
| " for key " << key_path; |
| } |
| } else { |
| // TODO(erikkay): find a way to get this into about:extensions |
| LOG(WARNING) << "Missing value " << kRegistryExtensionPath << |
| " for key " << key_path; |
| } |
| } |
| ++iterator; |
| } |
| #endif |
| } |
| |
| bool ExtensionsServiceBackend::CheckExternalUninstall( |
| DictionaryValue* extension_prefs, const FilePath& version_path, |
| const std::string& id) { |
| // First check the preferences for the kill-bit. |
| Extension::Location location = Extension::INVALID; |
| DictionaryValue* extension = NULL; |
| if (extension_prefs->GetDictionary(ASCIIToWide(id), &extension)) { |
| Extension::State state; |
| if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) && |
| location == Extension::EXTERNAL_PREF) { |
| return extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && |
| state == Extension::KILLBIT; |
| } |
| } |
| |
| #if defined(OS_WIN) |
| if (location == Extension::EXTERNAL_REGISTRY) { |
| HKEY reg_root = HKEY_LOCAL_MACHINE; |
| RegKey key; |
| std::wstring key_path = ASCIIToWide(registry_path_); |
| key_path.append(L"\\"); |
| key_path.append(ASCIIToWide(id)); |
| |
| // If the key doesn't exist, then we should uninstall. |
| return !key.Open(reg_root, key_path.c_str()); |
| } |
| #endif |
| |
| return false; |
| } |
| |
| // Assumes that the extension isn't currently loaded or in use. |
| void ExtensionsServiceBackend::UninstallExtension( |
| const std::string& extension_id) { |
| // First, delete the Current Version file. If the directory delete fails, then |
| // at least the extension won't be loaded again. |
| FilePath extension_directory = install_directory_.AppendASCII(extension_id); |
| |
| if (!file_util::PathExists(extension_directory)) { |
| LOG(WARNING) << "Asked to remove a non-existent extension " << extension_id; |
| return; |
| } |
| |
| FilePath current_version_file = extension_directory.AppendASCII( |
| ExtensionsService::kCurrentVersionFileName); |
| if (!file_util::PathExists(current_version_file)) { |
| LOG(WARNING) << "Extension " << extension_id |
| << " does not have a Current Version file."; |
| } else { |
| if (!file_util::Delete(current_version_file, false)) { |
| LOG(WARNING) << "Could not delete Current Version file for extension " |
| << extension_id; |
| return; |
| } |
| } |
| |
| // Ok, now try and delete the entire rest of the directory. One major place |
| // this can fail is if the extension contains a plugin (stupid plugins). It's |
| // not a big deal though, because we'll notice next time we startup that the |
| // Current Version file is gone and finish the delete then. |
| if (!file_util::Delete(extension_directory, true)) { |
| LOG(WARNING) << "Could not delete directory for extension " |
| << extension_id; |
| } |
| } |
| |
| bool ExtensionsServiceBackend::ShouldInstall(const std::string& id, |
| const std::string& version) { |
| FilePath dir(install_directory_.AppendASCII(id.c_str())); |
| std::string current_version; |
| if (ReadCurrentVersion(dir, ¤t_version)) { |
| return CheckCurrentVersion(version, current_version, dir); |
| } |
| return true; |
| } |