• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
CreateDummyExtension(const BundleInstaller::Item & item,base::DictionaryValue * manifest,content::BrowserContext * browser_context)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 
IsAppPredicate(scoped_refptr<const Extension> extension)66 bool IsAppPredicate(scoped_refptr<const Extension> extension) {
67   return extension->is_app();
68 }
69 
70 struct MatchIdFunctor {
MatchIdFunctorextensions::__anondc73a93c0111::MatchIdFunctor71   explicit MatchIdFunctor(const std::string& id) : id(id) {}
operator ()extensions::__anondc73a93c0111::MatchIdFunctor72   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
SetAutoApproveForTesting(bool auto_approve)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 
Item()102 BundleInstaller::Item::Item() : state(STATE_PENDING) {}
103 
GetNameForDisplay()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 
BundleInstaller(Browser * browser,const BundleInstaller::ItemList & items)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 
GetItemsWithState(Item::State state) const124 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 
PromptForApproval(Delegate * delegate)136 void BundleInstaller::PromptForApproval(Delegate* delegate) {
137   delegate_ = delegate;
138 
139   AddRef();  // Balanced in ReportApproved() and ReportCanceled().
140 
141   ParseManifests();
142 }
143 
CompleteInstall(content::WebContents * web_contents,Delegate * delegate)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 
GetHeadingTextFor(Item::State state) const184 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 
~BundleInstaller()211 BundleInstaller::~BundleInstaller() {
212   BrowserList::RemoveObserver(this);
213 }
214 
ParseManifests()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 
ReportApproved()228 void BundleInstaller::ReportApproved() {
229   if (delegate_)
230     delegate_->OnBundleInstallApproved();
231 
232   Release();  // Balanced in PromptForApproval().
233 }
234 
ReportCanceled(bool user_initiated)235 void BundleInstaller::ReportCanceled(bool user_initiated) {
236   if (delegate_)
237     delegate_->OnBundleInstallCanceled(user_initiated);
238 
239   Release();  // Balanced in PromptForApproval().
240 }
241 
ReportComplete()242 void BundleInstaller::ReportComplete() {
243   if (delegate_)
244     delegate_->OnBundleInstallCompleted();
245 
246   Release();  // Balanced in CompleteInstall().
247 }
248 
ShowPromptIfDoneParsing()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 
ShowPrompt()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 
ShowInstalledBubbleIfDone()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 
OnWebstoreParseSuccess(const std::string & id,const SkBitmap & icon,base::DictionaryValue * manifest)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 
OnWebstoreParseFailure(const std::string & id,WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,const std::string & error_message)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 
InstallUIProceed()322 void BundleInstaller::InstallUIProceed() {
323   approved_ = true;
324   ReportApproved();
325 }
326 
InstallUIAbort(bool user_initiated)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 
OnExtensionInstallSuccess(const std::string & id)334 void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) {
335   items_[id].state = Item::STATE_INSTALLED;
336 
337   ShowInstalledBubbleIfDone();
338 }
339 
OnExtensionInstallFailure(const std::string & id,const std::string & error,WebstoreInstaller::FailureReason reason)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 
OnBrowserAdded(Browser * browser)354 void BundleInstaller::OnBrowserAdded(Browser* browser) {}
355 
OnBrowserRemoved(Browser * browser)356 void BundleInstaller::OnBrowserRemoved(Browser* browser) {
357   if (browser_ == browser)
358     browser_ = NULL;
359 }
360 
OnBrowserSetLastActive(Browser * browser)361 void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {}
362 
363 }  // namespace extensions
364