| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/crx_installer.h" | 5 #include "chrome/browser/extensions/crx_installer.h" |
| 6 | 6 |
| 7 #include "app/l10n_util.h" | 7 #include "app/l10n_util.h" |
| 8 #include "base/file_util.h" | 8 #include "base/file_util.h" |
| 9 #include "base/scoped_temp_dir.h" | 9 #include "base/scoped_temp_dir.h" |
| 10 #include "base/string_util.h" | 10 #include "base/string_util.h" |
| 11 #include "base/task.h" | 11 #include "base/task.h" |
| 12 #include "chrome/browser/browser_process.h" | 12 #include "chrome/browser/browser_process.h" |
| 13 #include "chrome/browser/extensions/extension_file_util.h" | 13 #include "chrome/browser/extensions/extension_file_util.h" |
| 14 #include "chrome/common/extensions/extension_error_reporter.h" | 14 #include "chrome/common/extensions/extension_error_reporter.h" |
| 15 #include "grit/chromium_strings.h" | 15 #include "grit/chromium_strings.h" |
| 16 | 16 |
| 17 #if defined(OS_WIN) | 17 namespace { |
| 18 #include "app/win_util.h" | 18 // Helper function to delete files. This is used to avoid ugly casts which |
| 19 #elif defined(OS_MACOSX) | 19 // would be necessary with PostMessage since file_util::Delete is overloaded. |
| 20 #include "base/scoped_cftyperef.h" | 20 static void DeleteFileHelper(const FilePath& path, bool recursive) { |
| 21 #include "base/sys_string_conversions.h" | 21 file_util::Delete(path, recursive); |
| 22 #include <CoreFoundation/CFUserNotification.h> | 22 } |
| 23 #endif | 23 } |
| 24 |
| 25 void CrxInstaller::Start(const FilePath& crx_path, |
| 26 const FilePath& install_directory, |
| 27 Extension::Location install_source, |
| 28 const std::string& expected_id, |
| 29 bool delete_crx, |
| 30 MessageLoop* file_loop, |
| 31 ExtensionsService* frontend, |
| 32 CrxInstallerClient* client) { |
| 33 // Note: We don't keep a reference because this object manages its own |
| 34 // lifetime. |
| 35 new CrxInstaller(crx_path, install_directory, install_source, expected_id, |
| 36 delete_crx, file_loop, frontend, client); |
| 37 } |
| 24 | 38 |
| 25 CrxInstaller::CrxInstaller(const FilePath& crx_path, | 39 CrxInstaller::CrxInstaller(const FilePath& crx_path, |
| 26 const FilePath& install_directory, | 40 const FilePath& install_directory, |
| 27 Extension::Location install_source, | 41 Extension::Location install_source, |
| 28 const std::string& expected_id, | 42 const std::string& expected_id, |
| 29 bool extensions_enabled, | |
| 30 bool is_from_gallery, | |
| 31 bool show_prompts, | |
| 32 bool delete_crx, | 43 bool delete_crx, |
| 33 MessageLoop* file_loop, | 44 MessageLoop* file_loop, |
| 34 ExtensionsService* frontend) | 45 ExtensionsService* frontend, |
| 46 CrxInstallerClient* client) |
| 35 : crx_path_(crx_path), | 47 : crx_path_(crx_path), |
| 36 install_directory_(install_directory), | 48 install_directory_(install_directory), |
| 37 install_source_(install_source), | 49 install_source_(install_source), |
| 38 expected_id_(expected_id), | 50 expected_id_(expected_id), |
| 39 extensions_enabled_(extensions_enabled), | |
| 40 is_from_gallery_(is_from_gallery), | |
| 41 show_prompts_(show_prompts), | |
| 42 delete_crx_(delete_crx), | 51 delete_crx_(delete_crx), |
| 43 file_loop_(file_loop), | 52 file_loop_(file_loop), |
| 44 ui_loop_(MessageLoop::current()) { | 53 ui_loop_(MessageLoop::current()), |
| 54 frontend_(frontend), |
| 55 client_(client) { |
| 45 | 56 |
| 46 // Note: this is a refptr so that we keep the frontend alive long enough to | 57 extensions_enabled_ = frontend_->extensions_enabled(); |
| 47 // get our response. | 58 |
| 48 frontend_ = frontend; | |
| 49 unpacker_ = new SandboxedExtensionUnpacker( | 59 unpacker_ = new SandboxedExtensionUnpacker( |
| 50 crx_path, g_browser_process->resource_dispatcher_host(), this); | 60 crx_path, g_browser_process->resource_dispatcher_host(), this); |
| 51 | 61 |
| 52 file_loop->PostTask(FROM_HERE, NewRunnableMethod(unpacker_, | 62 file_loop->PostTask(FROM_HERE, NewRunnableMethod(unpacker_, |
| 53 &SandboxedExtensionUnpacker::Start)); | 63 &SandboxedExtensionUnpacker::Start)); |
| 54 } | 64 } |
| 55 | 65 |
| 66 CrxInstaller::~CrxInstaller() { |
| 67 // Delete the temp directory and crx file as necessary. Note that the |
| 68 // destructor might be called on any thread, so we post a task to the file |
| 69 // thread to make sure the delete happens there. |
| 70 if (!temp_dir_.value().empty()) { |
| 71 file_loop_->PostTask(FROM_HERE, NewRunnableFunction(&DeleteFileHelper, |
| 72 temp_dir_, true)); // recursive delete |
| 73 } |
| 74 |
| 75 if (delete_crx_) { |
| 76 file_loop_->PostTask(FROM_HERE, NewRunnableFunction(&DeleteFileHelper, |
| 77 crx_path_, false)); // non-recursive delete |
| 78 } |
| 79 } |
| 80 |
| 56 void CrxInstaller::OnUnpackFailure(const std::string& error_message) { | 81 void CrxInstaller::OnUnpackFailure(const std::string& error_message) { |
| 82 DCHECK(MessageLoop::current() == file_loop_); |
| 57 ReportFailureFromFileThread(error_message); | 83 ReportFailureFromFileThread(error_message); |
| 58 } | 84 } |
| 59 | 85 |
| 60 void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, | 86 void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, |
| 61 const FilePath& extension_dir, | 87 const FilePath& extension_dir, |
| 62 Extension* extension) { | 88 Extension* extension) { |
| 89 DCHECK(MessageLoop::current() == file_loop_); |
| 90 |
| 63 // Note: We take ownership of |extension| and |temp_dir|. | 91 // Note: We take ownership of |extension| and |temp_dir|. |
| 64 extension_.reset(extension); | 92 extension_.reset(extension); |
| 65 temp_dir_ = temp_dir; | 93 temp_dir_ = temp_dir; |
| 66 | 94 |
| 67 // temp_dir_deleter is stack allocated instead of a member of CrxInstaller, so | |
| 68 // that delete always happens on the file thread. | |
| 69 ScopedTempDir temp_dir_deleter; | |
| 70 temp_dir_deleter.Set(temp_dir); | |
| 71 | |
| 72 // The unpack dir we don't have to delete explicity since it is a child of | 95 // The unpack dir we don't have to delete explicity since it is a child of |
| 73 // the temp dir. | 96 // the temp dir. |
| 74 unpacked_extension_root_ = extension_dir; | 97 unpacked_extension_root_ = extension_dir; |
| 75 DCHECK(file_util::ContainsPath(temp_dir_, unpacked_extension_root_)); | 98 DCHECK(file_util::ContainsPath(temp_dir_, unpacked_extension_root_)); |
| 76 | 99 |
| 77 // If we were supposed to delete the source file, we can do that now. | |
| 78 if (delete_crx_) | |
| 79 file_util::Delete(crx_path_, false); // non-recursive | |
| 80 | |
| 81 // Determine whether to allow installation. We always allow themes and | 100 // Determine whether to allow installation. We always allow themes and |
| 82 // external installs. | 101 // external installs. |
| 83 if (!extensions_enabled_ && !extension->IsTheme() && | 102 if (!extensions_enabled_ && !extension->IsTheme() && |
| 84 !Extension::IsExternalLocation(install_source_)) { | 103 !Extension::IsExternalLocation(install_source_)) { |
| 85 ReportFailureFromFileThread("Extensions are not enabled."); | 104 ReportFailureFromFileThread("Extensions are not enabled."); |
| 86 return; | 105 return; |
| 87 } | 106 } |
| 88 | 107 |
| 89 // Make sure the expected id matches. | 108 // Make sure the expected id matches. |
| 90 // TODO(aa): Also support expected version? | 109 // TODO(aa): Also support expected version? |
| 91 if (!expected_id_.empty() && expected_id_ != extension->id()) { | 110 if (!expected_id_.empty() && expected_id_ != extension->id()) { |
| 92 ReportFailureFromFileThread( | 111 ReportFailureFromFileThread(StringPrintf( |
| 93 StringPrintf("ID in new extension manifest (%s) does not match " | 112 "ID in new extension manifest (%s) does not match expected id (%s)", |
| 94 "expected id (%s)", | 113 extension->id().c_str(), |
| 95 extension->id().c_str(), | 114 expected_id_.c_str())); |
| 96 expected_id_.c_str())); | |
| 97 return; | 115 return; |
| 98 } | 116 } |
| 99 | 117 |
| 100 // Show the confirm UI if necessary. | 118 if (client_.get()) { |
| 101 // NOTE: We also special case themes to not have a dialog, because we show | 119 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 102 // a special infobar UI for them instead. | 120 &CrxInstaller::ConfirmInstall)); |
| 103 if (show_prompts_ && !extension->IsTheme()) { | 121 } else { |
| 104 if (!ConfirmInstall()) | 122 CompleteInstall(); |
| 105 return; // error reported by ConfirmInstall() | 123 } |
| 124 } |
| 125 |
| 126 void CrxInstaller::ConfirmInstall() { |
| 127 if (!client_->ConfirmInstall(extension_.get())) { |
| 128 // We're done. Since we don't post any more tasks to ourselves, our ref |
| 129 // count should go to zero and we die. The destructor will clean up the temp |
| 130 // dir. |
| 131 return; |
| 106 } | 132 } |
| 107 | 133 |
| 108 CompleteInstall(); | 134 file_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 109 } | 135 &CrxInstaller::CompleteInstall)); |
| 110 | |
| 111 bool CrxInstaller::ConfirmInstall() { | |
| 112 #if defined(OS_WIN) | |
| 113 if (win_util::MessageBox(GetForegroundWindow(), | |
| 114 L"Are you sure you want to install this extension?\n\n" | |
| 115 L"You should only install extensions from sources you trust.", | |
| 116 l10n_util::GetString(IDS_PRODUCT_NAME).c_str(), | |
| 117 MB_OKCANCEL) != IDOK) { | |
| 118 ReportFailureFromFileThread("User did not allow extension to be " | |
| 119 "installed."); | |
| 120 return false; | |
| 121 } | |
| 122 #elif defined(OS_MACOSX) | |
| 123 // Using CoreFoundation to do this dialog is unimaginably lame but will do | |
| 124 // until the UI is redone. | |
| 125 scoped_cftyperef<CFStringRef> product_name( | |
| 126 base::SysWideToCFStringRef(l10n_util::GetString(IDS_PRODUCT_NAME))); | |
| 127 CFOptionFlags response; | |
| 128 if (kCFUserNotificationAlternateResponse == CFUserNotificationDisplayAlert( | |
| 129 0, kCFUserNotificationCautionAlertLevel, NULL, NULL, NULL, | |
| 130 product_name, | |
| 131 CFSTR("Are you sure you want to install this extension?\n\n" | |
| 132 "This is a temporary message and it will be removed when " | |
| 133 "extensions UI is finalized."), | |
| 134 NULL, CFSTR("Cancel"), NULL, &response)) { | |
| 135 ReportFailureFromFileThread("User did not allow extension to be " | |
| 136 "installed."); | |
| 137 return false; | |
| 138 } | |
| 139 #endif // OS_* | |
| 140 | |
| 141 return true; | |
| 142 } | 136 } |
| 143 | 137 |
| 144 void CrxInstaller::CompleteInstall() { | 138 void CrxInstaller::CompleteInstall() { |
| 139 DCHECK(MessageLoop::current() == file_loop_); |
| 140 |
| 145 FilePath version_dir; | 141 FilePath version_dir; |
| 146 Extension::InstallType install_type = Extension::INSTALL_ERROR; | 142 Extension::InstallType install_type = Extension::INSTALL_ERROR; |
| 147 std::string error_msg; | 143 std::string error_msg; |
| 148 if (!extension_file_util::InstallExtension(unpacked_extension_root_, | 144 if (!extension_file_util::InstallExtension(unpacked_extension_root_, |
| 149 install_directory_, | 145 install_directory_, |
| 150 extension_->id(), | 146 extension_->id(), |
| 151 extension_->VersionString(), | 147 extension_->VersionString(), |
| 152 &version_dir, | 148 &version_dir, |
| 153 &install_type, &error_msg)) { | 149 &install_type, &error_msg)) { |
| 154 ReportFailureFromFileThread(error_msg); | 150 ReportFailureFromFileThread(error_msg); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 166 if (install_type == Extension::REINSTALL) { | 162 if (install_type == Extension::REINSTALL) { |
| 167 // We use this as a signal to switch themes. | 163 // We use this as a signal to switch themes. |
| 168 ReportOverinstallFromFileThread(); | 164 ReportOverinstallFromFileThread(); |
| 169 return; | 165 return; |
| 170 } | 166 } |
| 171 | 167 |
| 172 ReportSuccessFromFileThread(); | 168 ReportSuccessFromFileThread(); |
| 173 } | 169 } |
| 174 | 170 |
| 175 void CrxInstaller::ReportFailureFromFileThread(const std::string& error) { | 171 void CrxInstaller::ReportFailureFromFileThread(const std::string& error) { |
| 172 DCHECK(MessageLoop::current() == file_loop_); |
| 176 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, | 173 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 177 &CrxInstaller::ReportFailureFromUIThread, error)); | 174 &CrxInstaller::ReportFailureFromUIThread, error)); |
| 178 } | 175 } |
| 179 | 176 |
| 180 void CrxInstaller::ReportFailureFromUIThread(const std::string& error) { | 177 void CrxInstaller::ReportFailureFromUIThread(const std::string& error) { |
| 181 ExtensionErrorReporter::GetInstance()->ReportError(error, show_prompts_); | 178 DCHECK(MessageLoop::current() == ui_loop_); |
| 179 |
| 180 // This isn't really necessary, it is only used because unit tests expect to |
| 181 // see errors get reported via this interface. |
| 182 // |
| 183 // TODO(aa): Need to go through unit tests and clean them up too, probably get |
| 184 // rid of this line. |
| 185 ExtensionErrorReporter::GetInstance()->ReportError(error, false); // quiet |
| 186 |
| 187 if (client_) |
| 188 client_->OnInstallFailure(error); |
| 182 } | 189 } |
| 183 | 190 |
| 184 void CrxInstaller::ReportOverinstallFromFileThread() { | 191 void CrxInstaller::ReportOverinstallFromFileThread() { |
| 185 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(frontend_.get(), | 192 DCHECK(MessageLoop::current() == file_loop_); |
| 186 &ExtensionsService::OnExtensionOverinstallAttempted, extension_->id())); | 193 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 194 &CrxInstaller::ReportOverinstallFromUIThread)); |
| 195 } |
| 196 |
| 197 void CrxInstaller::ReportOverinstallFromUIThread() { |
| 198 DCHECK(MessageLoop::current() == ui_loop_); |
| 199 |
| 200 if (client_.get()) |
| 201 client_->OnOverinstallAttempted(extension_.get()); |
| 202 |
| 203 frontend_->OnExtensionOverinstallAttempted(extension_->id()); |
| 187 } | 204 } |
| 188 | 205 |
| 189 void CrxInstaller::ReportSuccessFromFileThread() { | 206 void CrxInstaller::ReportSuccessFromFileThread() { |
| 207 DCHECK(MessageLoop::current() == file_loop_); |
| 208 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 209 &CrxInstaller::ReportSuccessFromUIThread)); |
| 210 } |
| 211 |
| 212 void CrxInstaller::ReportSuccessFromUIThread() { |
| 213 DCHECK(MessageLoop::current() == ui_loop_); |
| 214 |
| 215 // If there is a client, tell the client about installation. |
| 216 if (client_.get()) |
| 217 client_->OnInstallSuccess(extension_.get()); |
| 218 |
| 190 // Tell the frontend about the installation and hand off ownership of | 219 // Tell the frontend about the installation and hand off ownership of |
| 191 // extension_ to it. | 220 // extension_ to it. |
| 192 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(frontend_.get(), | 221 frontend_->OnExtensionInstalled(extension_.release()); |
| 193 &ExtensionsService::OnExtensionInstalled, extension_.release())); | 222 |
| 223 // We're done. We don't post any more tasks to ourselves so we are deleted |
| 224 // soon. |
| 194 } | 225 } |
| OLD | NEW |