1 // Copyright 2013 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/api/messaging/incognito_connectability.h"
6 
7 #include "base/bind.h"
8 #include "base/lazy_instance.h"
9 #include "base/logging.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h"
13 #include "chrome/browser/infobars/infobar_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "components/infobars/core/infobar.h"
17 #include "content/public/browser/web_contents.h"
18 #include "extensions/common/extension.h"
19 #include "ui/base/l10n/l10n_util.h"
20 
21 namespace extensions {
22 
23 namespace {
24 
25 IncognitoConnectability::ScopedAlertTracker::Mode g_alert_mode =
26     IncognitoConnectability::ScopedAlertTracker::INTERACTIVE;
27 int g_alert_count = 0;
28 
29 }  // namespace
30 
ScopedAlertTracker(Mode mode)31 IncognitoConnectability::ScopedAlertTracker::ScopedAlertTracker(Mode mode)
32     : last_checked_invocation_count_(g_alert_count) {
33   DCHECK_EQ(INTERACTIVE, g_alert_mode);
34   DCHECK_NE(INTERACTIVE, mode);
35   g_alert_mode = mode;
36 }
37 
~ScopedAlertTracker()38 IncognitoConnectability::ScopedAlertTracker::~ScopedAlertTracker() {
39   DCHECK_NE(INTERACTIVE, g_alert_mode);
40   g_alert_mode = INTERACTIVE;
41 }
42 
GetAndResetAlertCount()43 int IncognitoConnectability::ScopedAlertTracker::GetAndResetAlertCount() {
44   int result = g_alert_count - last_checked_invocation_count_;
45   last_checked_invocation_count_ = g_alert_count;
46   return result;
47 }
48 
IncognitoConnectability(content::BrowserContext * context)49 IncognitoConnectability::IncognitoConnectability(
50     content::BrowserContext* context) {
51   CHECK(context->IsOffTheRecord());
52 }
53 
~IncognitoConnectability()54 IncognitoConnectability::~IncognitoConnectability() {
55 }
56 
57 // static
Get(content::BrowserContext * context)58 IncognitoConnectability* IncognitoConnectability::Get(
59     content::BrowserContext* context) {
60   return BrowserContextKeyedAPIFactory<IncognitoConnectability>::Get(context);
61 }
62 
Query(const Extension * extension,content::WebContents * web_contents,const GURL & url,const base::Callback<void (bool)> & callback)63 void IncognitoConnectability::Query(
64     const Extension* extension,
65     content::WebContents* web_contents,
66     const GURL& url,
67     const base::Callback<void(bool)>& callback) {
68   GURL origin = url.GetOrigin();
69   if (origin.is_empty()) {
70     callback.Run(false);
71     return;
72   }
73 
74   if (IsInMap(extension, origin, allowed_origins_)) {
75     callback.Run(true);
76     return;
77   }
78 
79   if (IsInMap(extension, origin, disallowed_origins_)) {
80     callback.Run(false);
81     return;
82   }
83 
84   PendingOrigin& pending_origin =
85       pending_origins_[make_pair(extension->id(), origin)];
86   InfoBarService* infobar_service =
87       InfoBarService::FromWebContents(web_contents);
88   TabContext& tab_context = pending_origin[infobar_service];
89   tab_context.callbacks.push_back(callback);
90   if (tab_context.infobar) {
91     // This tab is already displaying an infobar for this extension and origin.
92     return;
93   }
94 
95   // We need to ask the user.
96   ++g_alert_count;
97 
98   switch (g_alert_mode) {
99     // Production code should always be using INTERACTIVE.
100     case ScopedAlertTracker::INTERACTIVE: {
101       int template_id =
102           extension->is_app()
103               ? IDS_EXTENSION_PROMPT_APP_CONNECT_FROM_INCOGNITO
104               : IDS_EXTENSION_PROMPT_EXTENSION_CONNECT_FROM_INCOGNITO;
105       tab_context.infobar = IncognitoConnectabilityInfoBarDelegate::Create(
106           infobar_service,
107           l10n_util::GetStringFUTF16(template_id,
108                                      base::UTF8ToUTF16(origin.spec()),
109                                      base::UTF8ToUTF16(extension->name())),
110           base::Bind(&IncognitoConnectability::OnInteractiveResponse,
111                      weak_factory_.GetWeakPtr(), extension->id(), origin,
112                      infobar_service));
113       break;
114     }
115 
116     // Testing code can override to always allow or deny.
117     case ScopedAlertTracker::ALWAYS_ALLOW:
118     case ScopedAlertTracker::ALWAYS_DENY:
119       OnInteractiveResponse(extension->id(), origin, infobar_service,
120                             g_alert_mode);
121       break;
122   }
123 }
124 
TabContext()125 IncognitoConnectability::TabContext::TabContext() : infobar(nullptr) {
126 }
127 
128 IncognitoConnectability::TabContext::TabContext(const TabContext& other) =
129     default;
130 
~TabContext()131 IncognitoConnectability::TabContext::~TabContext() {
132 }
133 
OnInteractiveResponse(const std::string & extension_id,const GURL & origin,InfoBarService * infobar_service,ScopedAlertTracker::Mode response)134 void IncognitoConnectability::OnInteractiveResponse(
135     const std::string& extension_id,
136     const GURL& origin,
137     InfoBarService* infobar_service,
138     ScopedAlertTracker::Mode response) {
139   switch (response) {
140     case ScopedAlertTracker::ALWAYS_ALLOW:
141       allowed_origins_[extension_id].insert(origin);
142       break;
143     case ScopedAlertTracker::ALWAYS_DENY:
144       disallowed_origins_[extension_id].insert(origin);
145       break;
146     default:
147       // Otherwise the user has not expressed an explicit preference and so
148       // nothing should be permanently recorded.
149       break;
150   }
151 
152   DCHECK(base::Contains(pending_origins_, make_pair(extension_id, origin)));
153   PendingOrigin& pending_origin =
154       pending_origins_[make_pair(extension_id, origin)];
155   DCHECK(base::Contains(pending_origin, infobar_service));
156 
157   std::vector<base::Callback<void(bool)>> callbacks;
158   if (response == ScopedAlertTracker::INTERACTIVE) {
159     // No definitive answer for this extension and origin. Execute only the
160     // callbacks associated with this tab.
161     TabContext& tab_context = pending_origin[infobar_service];
162     callbacks.swap(tab_context.callbacks);
163     pending_origin.erase(infobar_service);
164   } else {
165     // We have a definitive answer for this extension and origin. Close all
166     // other infobars and answer all the callbacks.
167     for (const auto& map_entry : pending_origin) {
168       InfoBarService* other_infobar_service = map_entry.first;
169       const TabContext& other_tab_context = map_entry.second;
170       if (other_infobar_service != infobar_service) {
171         // Disarm the delegate so that it doesn't think the infobar has been
172         // dismissed.
173         IncognitoConnectabilityInfoBarDelegate* delegate =
174             static_cast<IncognitoConnectabilityInfoBarDelegate*>(
175                 other_tab_context.infobar->delegate());
176         delegate->set_answered();
177         other_infobar_service->RemoveInfoBar(other_tab_context.infobar);
178       }
179       callbacks.insert(callbacks.end(), other_tab_context.callbacks.begin(),
180                        other_tab_context.callbacks.end());
181     }
182     pending_origins_.erase(make_pair(extension_id, origin));
183   }
184 
185   DCHECK(!callbacks.empty());
186   for (const auto& callback : callbacks) {
187     callback.Run(response == ScopedAlertTracker::ALWAYS_ALLOW);
188   }
189 }
190 
IsInMap(const Extension * extension,const GURL & origin,const ExtensionToOriginsMap & map)191 bool IncognitoConnectability::IsInMap(const Extension* extension,
192                                       const GURL& origin,
193                                       const ExtensionToOriginsMap& map) {
194   DCHECK_EQ(origin, origin.GetOrigin());
195   auto it = map.find(extension->id());
196   return it != map.end() && it->second.count(origin) > 0;
197 }
198 
199 static base::LazyInstance<
200     BrowserContextKeyedAPIFactory<IncognitoConnectability>>::DestructorAtExit
201     g_incognito_connectability_factory = LAZY_INSTANCE_INITIALIZER;
202 
203 // static
204 BrowserContextKeyedAPIFactory<IncognitoConnectability>*
GetFactoryInstance()205 IncognitoConnectability::GetFactoryInstance() {
206   return g_incognito_connectability_factory.Pointer();
207 }
208 
209 }  // namespace extensions
210