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...) 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 |