1 // Copyright (c) 2012 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/bundle_installer.h" 6 7 #include <algorithm> 8 #include <string> 9 #include <vector> 10 11 #include "base/command_line.h" 12 #include "base/i18n/rtl.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/extensions/crx_installer.h" 16 #include "chrome/browser/extensions/permissions_updater.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/ui/browser.h" 19 #include "chrome/browser/ui/browser_finder.h" 20 #include "chrome/browser/ui/browser_list.h" 21 #include "chrome/browser/ui/tabs/tab_strip_model.h" 22 #include "chrome/common/chrome_switches.h" 23 #include "chrome/grit/generated_resources.h" 24 #include "content/public/browser/web_contents.h" 25 #include "extensions/common/extension.h" 26 #include "extensions/common/permissions/permission_set.h" 27 #include "extensions/common/permissions/permissions_data.h" 28 #include "ui/base/l10n/l10n_util.h" 29 30 namespace extensions { 31 32 namespace { 33 34 enum AutoApproveForTest { 35 DO_NOT_SKIP = 0, 36 PROCEED, 37 ABORT 38 }; 39 40 AutoApproveForTest g_auto_approve_for_test = DO_NOT_SKIP; 41 42 // Creates a dummy extension and sets the manifest's name to the item's 43 // localized name. 44 scoped_refptr<Extension> CreateDummyExtension( 45 const BundleInstaller::Item& item, 46 base::DictionaryValue* manifest, 47 content::BrowserContext* browser_context) { 48 // We require localized names so we can have nice error messages when we can't 49 // parse an extension manifest. 50 CHECK(!item.localized_name.empty()); 51 52 std::string error; 53 scoped_refptr<Extension> extension = Extension::Create(base::FilePath(), 54 Manifest::INTERNAL, 55 *manifest, 56 Extension::NO_FLAGS, 57 item.id, 58 &error); 59 // Initialize permissions so that withheld permissions are displayed properly 60 // in the install prompt. 61 PermissionsUpdater(browser_context, PermissionsUpdater::INIT_FLAG_TRANSIENT) 62 .InitializePermissions(extension.get()); 63 return extension; 64 } 65 66 bool IsAppPredicate(scoped_refptr<const Extension> extension) { 67 return extension->is_app(); 68 } 69 70 struct MatchIdFunctor { 71 explicit MatchIdFunctor(const std::string& id) : id(id) {} 72 bool operator()(scoped_refptr<const Extension> extension) { 73 return extension->id() == id; 74 } 75 std::string id; 76 }; 77 78 // Holds the message IDs for BundleInstaller::GetHeadingTextFor. 79 const int kHeadingIds[3][4] = { 80 { 81 0, 82 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSIONS, 83 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_APPS, 84 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSION_APPS 85 }, 86 { 87 0, 88 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS, 89 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS, 90 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS 91 } 92 }; 93 94 } // namespace 95 96 // static 97 void BundleInstaller::SetAutoApproveForTesting(bool auto_approve) { 98 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); 99 g_auto_approve_for_test = auto_approve ? PROCEED : ABORT; 100 } 101 102 BundleInstaller::Item::Item() : state(STATE_PENDING) {} 103 104 base::string16 BundleInstaller::Item::GetNameForDisplay() { 105 base::string16 name = base::UTF8ToUTF16(localized_name); 106 base::i18n::AdjustStringForLocaleDirection(&name); 107 return l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE, name); 108 } 109 110 BundleInstaller::BundleInstaller(Browser* browser, 111 const BundleInstaller::ItemList& items) 112 : approved_(false), 113 browser_(browser), 114 host_desktop_type_(browser->host_desktop_type()), 115 profile_(browser->profile()), 116 delegate_(NULL) { 117 BrowserList::AddObserver(this); 118 for (size_t i = 0; i < items.size(); ++i) { 119 items_[items[i].id] = items[i]; 120 items_[items[i].id].state = Item::STATE_PENDING; 121 } 122 } 123 124 BundleInstaller::ItemList BundleInstaller::GetItemsWithState( 125 Item::State state) const { 126 ItemList list; 127 128 for (ItemMap::const_iterator i = items_.begin(); i != items_.end(); ++i) { 129 if (i->second.state == state) 130 list.push_back(i->second); 131 } 132 133 return list; 134 } 135 136 void BundleInstaller::PromptForApproval(Delegate* delegate) { 137 delegate_ = delegate; 138 139 AddRef(); // Balanced in ReportApproved() and ReportCanceled(). 140 141 ParseManifests(); 142 } 143 144 void BundleInstaller::CompleteInstall(content::WebContents* web_contents, 145 Delegate* delegate) { 146 CHECK(approved_); 147 148 delegate_ = delegate; 149 150 AddRef(); // Balanced in ReportComplete(); 151 152 if (GetItemsWithState(Item::STATE_PENDING).empty()) { 153 ReportComplete(); 154 return; 155 } 156 157 // Start each WebstoreInstaller. 158 for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) { 159 if (i->second.state != Item::STATE_PENDING) 160 continue; 161 162 // Since we've already confirmed the permissions, create an approval that 163 // lets CrxInstaller bypass the prompt. 164 scoped_ptr<WebstoreInstaller::Approval> approval( 165 WebstoreInstaller::Approval::CreateWithNoInstallPrompt( 166 profile_, 167 i->first, 168 scoped_ptr<base::DictionaryValue>( 169 parsed_manifests_[i->first]->DeepCopy()), true)); 170 approval->use_app_installed_bubble = false; 171 approval->skip_post_install_ui = true; 172 173 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller( 174 profile_, 175 this, 176 web_contents, 177 i->first, 178 approval.Pass(), 179 WebstoreInstaller::INSTALL_SOURCE_OTHER); 180 installer->Start(); 181 } 182 } 183 184 base::string16 BundleInstaller::GetHeadingTextFor(Item::State state) const { 185 // For STATE_FAILED, we can't tell if the items were apps or extensions 186 // so we always show the same message. 187 if (state == Item::STATE_FAILED) { 188 if (GetItemsWithState(state).size()) 189 return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING); 190 return base::string16(); 191 } 192 193 size_t total = GetItemsWithState(state).size(); 194 size_t apps = std::count_if( 195 dummy_extensions_.begin(), dummy_extensions_.end(), &IsAppPredicate); 196 197 bool has_apps = apps > 0; 198 bool has_extensions = apps < total; 199 size_t index = (has_extensions << 0) + (has_apps << 1); 200 201 CHECK_LT(static_cast<size_t>(state), arraysize(kHeadingIds)); 202 CHECK_LT(index, arraysize(kHeadingIds[state])); 203 204 int msg_id = kHeadingIds[state][index]; 205 if (!msg_id) 206 return base::string16(); 207 208 return l10n_util::GetStringUTF16(msg_id); 209 } 210 211 BundleInstaller::~BundleInstaller() { 212 BrowserList::RemoveObserver(this); 213 } 214 215 void BundleInstaller::ParseManifests() { 216 if (items_.empty()) { 217 ReportCanceled(false); 218 return; 219 } 220 221 for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) { 222 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( 223 this, i->first, i->second.manifest, std::string(), GURL(), NULL); 224 helper->Start(); 225 } 226 } 227 228 void BundleInstaller::ReportApproved() { 229 if (delegate_) 230 delegate_->OnBundleInstallApproved(); 231 232 Release(); // Balanced in PromptForApproval(). 233 } 234 235 void BundleInstaller::ReportCanceled(bool user_initiated) { 236 if (delegate_) 237 delegate_->OnBundleInstallCanceled(user_initiated); 238 239 Release(); // Balanced in PromptForApproval(). 240 } 241 242 void BundleInstaller::ReportComplete() { 243 if (delegate_) 244 delegate_->OnBundleInstallCompleted(); 245 246 Release(); // Balanced in CompleteInstall(). 247 } 248 249 void BundleInstaller::ShowPromptIfDoneParsing() { 250 // We don't prompt until all the manifests have been parsed. 251 ItemList pending_items = GetItemsWithState(Item::STATE_PENDING); 252 if (pending_items.size() != dummy_extensions_.size()) 253 return; 254 255 ShowPrompt(); 256 } 257 258 void BundleInstaller::ShowPrompt() { 259 // Abort if we couldn't create any Extensions out of the manifests. 260 if (dummy_extensions_.empty()) { 261 ReportCanceled(false); 262 return; 263 } 264 265 scoped_refptr<PermissionSet> permissions; 266 for (size_t i = 0; i < dummy_extensions_.size(); ++i) { 267 permissions = PermissionSet::CreateUnion( 268 permissions.get(), 269 dummy_extensions_[i]->permissions_data()->active_permissions().get()); 270 } 271 272 if (g_auto_approve_for_test == PROCEED) { 273 InstallUIProceed(); 274 } else if (g_auto_approve_for_test == ABORT) { 275 InstallUIAbort(true); 276 } else { 277 Browser* browser = browser_; 278 if (!browser) { 279 // The browser that we got initially could have gone away during our 280 // thread hopping. 281 browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_); 282 } 283 content::WebContents* web_contents = NULL; 284 if (browser) 285 web_contents = browser->tab_strip_model()->GetActiveWebContents(); 286 install_ui_.reset(new ExtensionInstallPrompt(web_contents)); 287 install_ui_->ConfirmBundleInstall(this, permissions.get()); 288 } 289 } 290 291 void BundleInstaller::ShowInstalledBubbleIfDone() { 292 // We're ready to show the installed bubble when no items are pending. 293 if (!GetItemsWithState(Item::STATE_PENDING).empty()) 294 return; 295 296 if (browser_) 297 ShowInstalledBubble(this, browser_); 298 299 ReportComplete(); 300 } 301 302 void BundleInstaller::OnWebstoreParseSuccess( 303 const std::string& id, 304 const SkBitmap& icon, 305 base::DictionaryValue* manifest) { 306 dummy_extensions_.push_back( 307 CreateDummyExtension(items_[id], manifest, profile_)); 308 parsed_manifests_[id] = linked_ptr<base::DictionaryValue>(manifest); 309 310 ShowPromptIfDoneParsing(); 311 } 312 313 void BundleInstaller::OnWebstoreParseFailure( 314 const std::string& id, 315 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code, 316 const std::string& error_message) { 317 items_[id].state = Item::STATE_FAILED; 318 319 ShowPromptIfDoneParsing(); 320 } 321 322 void BundleInstaller::InstallUIProceed() { 323 approved_ = true; 324 ReportApproved(); 325 } 326 327 void BundleInstaller::InstallUIAbort(bool user_initiated) { 328 for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) 329 i->second.state = Item::STATE_FAILED; 330 331 ReportCanceled(user_initiated); 332 } 333 334 void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) { 335 items_[id].state = Item::STATE_INSTALLED; 336 337 ShowInstalledBubbleIfDone(); 338 } 339 340 void BundleInstaller::OnExtensionInstallFailure( 341 const std::string& id, 342 const std::string& error, 343 WebstoreInstaller::FailureReason reason) { 344 items_[id].state = Item::STATE_FAILED; 345 346 ExtensionList::iterator i = std::find_if( 347 dummy_extensions_.begin(), dummy_extensions_.end(), MatchIdFunctor(id)); 348 CHECK(dummy_extensions_.end() != i); 349 dummy_extensions_.erase(i); 350 351 ShowInstalledBubbleIfDone(); 352 } 353 354 void BundleInstaller::OnBrowserAdded(Browser* browser) {} 355 356 void BundleInstaller::OnBrowserRemoved(Browser* browser) { 357 if (browser_ == browser) 358 browser_ = NULL; 359 } 360 361 void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {} 362 363 } // namespace extensions 364