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/browser/extensions/crx_installer.h"
6
7 #include <map>
8 #include <set>
9
10 #include "base/file_util.h"
11 #include "base/lazy_instance.h"
12 #include "base/memory/scoped_temp_dir.h"
13 #include "base/metrics/histogram.h"
14 #include "base/path_service.h"
15 #include "base/stl_util-inl.h"
16 #include "base/stringprintf.h"
17 #include "base/task.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/time.h"
20 #include "base/utf_string_conversions.h"
21 #include "base/version.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/extensions/convert_user_script.h"
24 #include "chrome/browser/extensions/convert_web_app.h"
25 #include "chrome/browser/extensions/extension_error_reporter.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/shell_integration.h"
28 #include "chrome/browser/web_applications/web_app.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/extensions/extension_constants.h"
31 #include "chrome/common/extensions/extension_file_util.h"
32 #include "content/browser/browser_thread.h"
33 #include "content/common/notification_service.h"
34 #include "content/common/notification_type.h"
35 #include "grit/chromium_strings.h"
36 #include "grit/generated_resources.h"
37 #include "grit/theme_resources.h"
38 #include "third_party/skia/include/core/SkBitmap.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/base/resource/resource_bundle.h"
41
42 namespace {
43
44 struct WhitelistedInstallData {
WhitelistedInstallData__anone89c7e550111::WhitelistedInstallData45 WhitelistedInstallData() {}
46 std::set<std::string> ids;
47 std::map<std::string, linked_ptr<DictionaryValue> > manifests;
48 };
49
50 static base::LazyInstance<WhitelistedInstallData>
51 g_whitelisted_install_data(base::LINKER_INITIALIZED);
52
53 } // namespace
54
55 // static
SetWhitelistedInstallId(const std::string & id)56 void CrxInstaller::SetWhitelistedInstallId(const std::string& id) {
57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
58 g_whitelisted_install_data.Get().ids.insert(id);
59 }
60
61 // static
SetWhitelistedManifest(const std::string & id,DictionaryValue * parsed_manifest)62 void CrxInstaller::SetWhitelistedManifest(const std::string& id,
63 DictionaryValue* parsed_manifest) {
64 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
65 WhitelistedInstallData& data = g_whitelisted_install_data.Get();
66 data.manifests[id] = linked_ptr<DictionaryValue>(parsed_manifest);
67 }
68
69 // static
GetWhitelistedManifest(const std::string & id)70 const DictionaryValue* CrxInstaller::GetWhitelistedManifest(
71 const std::string& id) {
72 WhitelistedInstallData& data = g_whitelisted_install_data.Get();
73 if (ContainsKey(data.manifests, id))
74 return data.manifests[id].get();
75 else
76 return NULL;
77 }
78
79 // static
RemoveWhitelistedManifest(const std::string & id)80 DictionaryValue* CrxInstaller::RemoveWhitelistedManifest(
81 const std::string& id) {
82 WhitelistedInstallData& data = g_whitelisted_install_data.Get();
83 if (ContainsKey(data.manifests, id)) {
84 DictionaryValue* manifest = data.manifests[id].release();
85 data.manifests.erase(id);
86 return manifest;
87 }
88 return NULL;
89 }
90
91 // static
IsIdWhitelisted(const std::string & id)92 bool CrxInstaller::IsIdWhitelisted(const std::string& id) {
93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
94 std::set<std::string>& ids = g_whitelisted_install_data.Get().ids;
95 return ContainsKey(ids, id);
96 }
97
98 // static
ClearWhitelistedInstallId(const std::string & id)99 bool CrxInstaller::ClearWhitelistedInstallId(const std::string& id) {
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
101 std::set<std::string>& ids = g_whitelisted_install_data.Get().ids;
102 if (ContainsKey(ids, id)) {
103 ids.erase(id);
104 return true;
105 }
106 return false;
107 }
108
CrxInstaller(ExtensionService * frontend,ExtensionInstallUI * client)109 CrxInstaller::CrxInstaller(ExtensionService* frontend,
110 ExtensionInstallUI* client)
111 : install_directory_(frontend->install_directory()),
112 install_source_(Extension::INTERNAL),
113 extensions_enabled_(frontend->extensions_enabled()),
114 delete_source_(false),
115 is_gallery_install_(false),
116 create_app_shortcut_(false),
117 frontend_(frontend),
118 client_(client),
119 apps_require_extension_mime_type_(false),
120 allow_silent_install_(false) {
121 }
122
~CrxInstaller()123 CrxInstaller::~CrxInstaller() {
124 // Delete the temp directory and crx file as necessary. Note that the
125 // destructor might be called on any thread, so we post a task to the file
126 // thread to make sure the delete happens there.
127 if (!temp_dir_.value().empty()) {
128 BrowserThread::PostTask(
129 BrowserThread::FILE, FROM_HERE,
130 NewRunnableFunction(
131 &extension_file_util::DeleteFile, temp_dir_, true));
132 }
133
134 if (delete_source_) {
135 BrowserThread::PostTask(
136 BrowserThread::FILE, FROM_HERE,
137 NewRunnableFunction(
138 &extension_file_util::DeleteFile, source_file_, false));
139 }
140
141 // Make sure the UI is deleted on the ui thread.
142 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, client_);
143 client_ = NULL;
144 }
145
InstallCrx(const FilePath & source_file)146 void CrxInstaller::InstallCrx(const FilePath& source_file) {
147 source_file_ = source_file;
148
149 scoped_refptr<SandboxedExtensionUnpacker> unpacker(
150 new SandboxedExtensionUnpacker(
151 source_file,
152 g_browser_process->resource_dispatcher_host(),
153 this));
154
155 BrowserThread::PostTask(
156 BrowserThread::FILE, FROM_HERE,
157 NewRunnableMethod(
158 unpacker.get(), &SandboxedExtensionUnpacker::Start));
159 }
160
InstallUserScript(const FilePath & source_file,const GURL & original_url)161 void CrxInstaller::InstallUserScript(const FilePath& source_file,
162 const GURL& original_url) {
163 DCHECK(!original_url.is_empty());
164
165 source_file_ = source_file;
166 original_url_ = original_url;
167
168 BrowserThread::PostTask(
169 BrowserThread::FILE, FROM_HERE,
170 NewRunnableMethod(this, &CrxInstaller::ConvertUserScriptOnFileThread));
171 }
172
ConvertUserScriptOnFileThread()173 void CrxInstaller::ConvertUserScriptOnFileThread() {
174 std::string error;
175 scoped_refptr<Extension> extension =
176 ConvertUserScriptToExtension(source_file_, original_url_, &error);
177 if (!extension) {
178 ReportFailureFromFileThread(error);
179 return;
180 }
181
182 OnUnpackSuccess(extension->path(), extension->path(), extension);
183 }
184
InstallWebApp(const WebApplicationInfo & web_app)185 void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
186 BrowserThread::PostTask(
187 BrowserThread::FILE, FROM_HERE,
188 NewRunnableMethod(this, &CrxInstaller::ConvertWebAppOnFileThread,
189 web_app));
190 }
191
ConvertWebAppOnFileThread(const WebApplicationInfo & web_app)192 void CrxInstaller::ConvertWebAppOnFileThread(
193 const WebApplicationInfo& web_app) {
194 std::string error;
195 scoped_refptr<Extension> extension(
196 ConvertWebAppToExtension(web_app, base::Time::Now()));
197 if (!extension) {
198 // Validation should have stopped any potential errors before getting here.
199 NOTREACHED() << "Could not convert web app to extension.";
200 return;
201 }
202
203 // TODO(aa): conversion data gets lost here :(
204
205 OnUnpackSuccess(extension->path(), extension->path(), extension);
206 }
207
AllowInstall(const Extension * extension,std::string * error)208 bool CrxInstaller::AllowInstall(const Extension* extension,
209 std::string* error) {
210 DCHECK(error);
211
212 // Make sure the expected id matches.
213 if (!expected_id_.empty() && expected_id_ != extension->id()) {
214 *error = base::StringPrintf(
215 "ID in new CRX manifest (%s) does not match expected id (%s)",
216 extension->id().c_str(),
217 expected_id_.c_str());
218 return false;
219 }
220
221 if (expected_version_.get() &&
222 !expected_version_->Equals(*extension->version())) {
223 *error = base::StringPrintf(
224 "Version in new CRX %s manifest (%s) does not match expected "
225 "version (%s)",
226 extension->id().c_str(),
227 expected_version_->GetString().c_str(),
228 extension->version()->GetString().c_str());
229 return false;
230 }
231
232 // The checks below are skipped for themes and external installs.
233 if (extension->is_theme() || Extension::IsExternalLocation(install_source_))
234 return true;
235
236 if (!extensions_enabled_) {
237 *error = "Extensions are not enabled.";
238 return false;
239 }
240
241 if (extension_->is_app()) {
242 // If the app was downloaded, apps_require_extension_mime_type_
243 // will be set. In this case, check that it was served with the
244 // right mime type. Make an exception for file URLs, which come
245 // from the users computer and have no headers.
246 if (!original_url_.SchemeIsFile() &&
247 apps_require_extension_mime_type_ &&
248 original_mime_type_ != Extension::kMimeType) {
249 *error = base::StringPrintf(
250 "Apps must be served with content type %s.",
251 Extension::kMimeType);
252 return false;
253 }
254
255 // If the client_ is NULL, then the app is either being installed via
256 // an internal mechanism like sync, external_extensions, or default apps.
257 // In that case, we don't want to enforce things like the install origin.
258 if (!is_gallery_install_ && client_) {
259 // For apps with a gallery update URL, require that they be installed
260 // from the gallery.
261 // TODO(erikkay) Apply this rule for paid extensions and themes as well.
262 if (extension->UpdatesFromGallery()) {
263 *error = l10n_util::GetStringFUTF8(
264 IDS_EXTENSION_DISALLOW_NON_DOWNLOADED_GALLERY_INSTALLS,
265 l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE));
266 return false;
267 }
268
269 // For self-hosted apps, verify that the entire extent is on the same
270 // host (or a subdomain of the host) the download happened from. There's
271 // no way for us to verify that the app controls any other hosts.
272 URLPattern pattern(UserScript::kValidUserScriptSchemes);
273 pattern.set_host(original_url_.host());
274 pattern.set_match_subdomains(true);
275
276 ExtensionExtent::PatternList patterns =
277 extension_->web_extent().patterns();
278 for (size_t i = 0; i < patterns.size(); ++i) {
279 if (!pattern.MatchesHost(patterns[i].host())) {
280 *error = base::StringPrintf(
281 "Apps must be served from the host that they affect.");
282 return false;
283 }
284 }
285 }
286 }
287
288 return true;
289 }
290
OnUnpackFailure(const std::string & error_message)291 void CrxInstaller::OnUnpackFailure(const std::string& error_message) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
293 ReportFailureFromFileThread(error_message);
294 }
295
OnUnpackSuccess(const FilePath & temp_dir,const FilePath & extension_dir,const Extension * extension)296 void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir,
297 const FilePath& extension_dir,
298 const Extension* extension) {
299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
300
301 // Note: We take ownership of |extension| and |temp_dir|.
302 extension_ = extension;
303 temp_dir_ = temp_dir;
304
305 // We don't have to delete the unpack dir explicity since it is a child of
306 // the temp dir.
307 unpacked_extension_root_ = extension_dir;
308
309 std::string error;
310 if (!AllowInstall(extension, &error)) {
311 ReportFailureFromFileThread(error);
312 return;
313 }
314
315 if (client_) {
316 Extension::DecodeIcon(extension_.get(), Extension::EXTENSION_ICON_LARGE,
317 &install_icon_);
318 }
319
320 BrowserThread::PostTask(
321 BrowserThread::UI, FROM_HERE,
322 NewRunnableMethod(this, &CrxInstaller::ConfirmInstall));
323 }
324
325 // Helper method to let us compare a whitelisted manifest with the actual
326 // downloaded extension's manifest, but ignoring the kPublicKey since the
327 // whitelisted manifest doesn't have that value.
EqualsIgnoringPublicKey(const DictionaryValue & extension_manifest,const DictionaryValue & whitelisted_manifest)328 static bool EqualsIgnoringPublicKey(
329 const DictionaryValue& extension_manifest,
330 const DictionaryValue& whitelisted_manifest) {
331 scoped_ptr<DictionaryValue> manifest_copy(extension_manifest.DeepCopy());
332 manifest_copy->Remove(extension_manifest_keys::kPublicKey, NULL);
333 return manifest_copy->Equals(&whitelisted_manifest);
334 }
335
ConfirmInstall()336 void CrxInstaller::ConfirmInstall() {
337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338 if (frontend_->extension_prefs()->IsExtensionBlacklisted(extension_->id())) {
339 VLOG(1) << "This extension: " << extension_->id()
340 << " is blacklisted. Install failed.";
341 ReportFailureFromUIThread("This extension is blacklisted.");
342 return;
343 }
344
345 if (!frontend_->extension_prefs()->IsExtensionAllowedByPolicy(
346 extension_->id())) {
347 ReportFailureFromUIThread("This extension is blacklisted by admin policy.");
348 return;
349 }
350
351 GURL overlapping_url;
352 const Extension* overlapping_extension =
353 frontend_->GetExtensionByOverlappingWebExtent(extension_->web_extent());
354 if (overlapping_extension &&
355 overlapping_extension->id() != extension_->id()) {
356 ReportFailureFromUIThread(l10n_util::GetStringFUTF8(
357 IDS_EXTENSION_OVERLAPPING_WEB_EXTENT,
358 UTF8ToUTF16(overlapping_extension->name())));
359 return;
360 }
361
362 current_version_ =
363 frontend_->extension_prefs()->GetVersionString(extension_->id());
364
365 // First see if it's whitelisted by id (the old mechanism).
366 bool whitelisted = ClearWhitelistedInstallId(extension_->id()) &&
367 extension_->plugins().empty() && is_gallery_install_;
368
369 // Now check if it's whitelisted by manifest.
370 scoped_ptr<DictionaryValue> whitelisted_manifest(
371 RemoveWhitelistedManifest(extension_->id()));
372 if (is_gallery_install_ && whitelisted_manifest.get()) {
373 if (!EqualsIgnoringPublicKey(*extension_->manifest_value(),
374 *whitelisted_manifest)) {
375 ReportFailureFromUIThread(
376 l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID));
377 return;
378 }
379 whitelisted = true;
380 }
381
382 if (client_ &&
383 (!allow_silent_install_ || !whitelisted)) {
384 AddRef(); // Balanced in Proceed() and Abort().
385 client_->ConfirmInstall(this, extension_.get());
386 } else {
387 BrowserThread::PostTask(
388 BrowserThread::FILE, FROM_HERE,
389 NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
390 }
391 return;
392 }
393
InstallUIProceed()394 void CrxInstaller::InstallUIProceed() {
395 BrowserThread::PostTask(
396 BrowserThread::FILE, FROM_HERE,
397 NewRunnableMethod(this, &CrxInstaller::CompleteInstall));
398
399 Release(); // balanced in ConfirmInstall().
400 }
401
InstallUIAbort()402 void CrxInstaller::InstallUIAbort() {
403 // Technically, this can be called for other reasons than the user hitting
404 // cancel, but they're rare.
405 ExtensionService::RecordPermissionMessagesHistogram(
406 extension_, "Extensions.Permissions_InstallCancel");
407
408 // Kill the theme loading bubble.
409 NotificationService* service = NotificationService::current();
410 service->Notify(NotificationType::NO_THEME_DETECTED,
411 Source<CrxInstaller>(this),
412 NotificationService::NoDetails());
413 Release(); // balanced in ConfirmInstall().
414
415 // We're done. Since we don't post any more tasks to ourself, our ref count
416 // should go to zero and we die. The destructor will clean up the temp dir.
417 }
418
CompleteInstall()419 void CrxInstaller::CompleteInstall() {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
421
422 if (!current_version_.empty()) {
423 scoped_ptr<Version> current_version(
424 Version::GetVersionFromString(current_version_));
425 if (current_version->CompareTo(*(extension_->version())) > 0) {
426 ReportFailureFromFileThread("Attempted to downgrade extension.");
427 return;
428 }
429 }
430
431 // See how long extension install paths are. This is important on
432 // windows, because file operations may fail if the path to a file
433 // exceeds a small constant. See crbug.com/69693 .
434 UMA_HISTOGRAM_CUSTOM_COUNTS(
435 "Extensions.CrxInstallDirPathLength",
436 install_directory_.value().length(), 0, 500, 100);
437
438 FilePath version_dir = extension_file_util::InstallExtension(
439 unpacked_extension_root_,
440 extension_->id(),
441 extension_->VersionString(),
442 install_directory_);
443 if (version_dir.empty()) {
444 ReportFailureFromFileThread(
445 l10n_util::GetStringUTF8(
446 IDS_EXTENSION_MOVE_DIRECTORY_TO_PROFILE_FAILED));
447 return;
448 }
449
450 // This is lame, but we must reload the extension because absolute paths
451 // inside the content scripts are established inside InitFromValue() and we
452 // just moved the extension.
453 // TODO(aa): All paths to resources inside extensions should be created
454 // lazily and based on the Extension's root path at that moment.
455 std::string error;
456 extension_ = extension_file_util::LoadExtension(
457 version_dir,
458 install_source_,
459 Extension::REQUIRE_KEY,
460 &error);
461 CHECK(error.empty()) << error;
462
463 ReportSuccessFromFileThread();
464 }
465
ReportFailureFromFileThread(const std::string & error)466 void CrxInstaller::ReportFailureFromFileThread(const std::string& error) {
467 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
468 BrowserThread::PostTask(
469 BrowserThread::UI, FROM_HERE,
470 NewRunnableMethod(this, &CrxInstaller::ReportFailureFromUIThread, error));
471 }
472
ReportFailureFromUIThread(const std::string & error)473 void CrxInstaller::ReportFailureFromUIThread(const std::string& error) {
474 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
475
476 NotificationService* service = NotificationService::current();
477 service->Notify(NotificationType::EXTENSION_INSTALL_ERROR,
478 Source<CrxInstaller>(this),
479 Details<const std::string>(&error));
480
481 // This isn't really necessary, it is only used because unit tests expect to
482 // see errors get reported via this interface.
483 //
484 // TODO(aa): Need to go through unit tests and clean them up too, probably get
485 // rid of this line.
486 ExtensionErrorReporter::GetInstance()->ReportError(error, false); // quiet
487
488 if (client_)
489 client_->OnInstallFailure(error);
490 }
491
ReportSuccessFromFileThread()492 void CrxInstaller::ReportSuccessFromFileThread() {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
494 BrowserThread::PostTask(
495 BrowserThread::UI, FROM_HERE,
496 NewRunnableMethod(this, &CrxInstaller::ReportSuccessFromUIThread));
497 }
498
ReportSuccessFromUIThread()499 void CrxInstaller::ReportSuccessFromUIThread() {
500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501
502 // If there is a client, tell the client about installation.
503 if (client_)
504 client_->OnInstallSuccess(extension_.get(), install_icon_.get());
505
506 // Tell the frontend about the installation and hand off ownership of
507 // extension_ to it.
508 frontend_->OnExtensionInstalled(extension_);
509 extension_ = NULL;
510
511 // We're done. We don't post any more tasks to ourselves so we are deleted
512 // soon.
513 }
514