1// Copyright 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/geolocation/geolocation_permission_context.h"
6
7#include <functional>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/prefs/pref_service.h"
13#include "base/strings/utf_string_conversions.h"
14#include "chrome/browser/content_settings/host_content_settings_map.h"
15#include "chrome/browser/content_settings/tab_specific_content_settings.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/tab_contents/tab_util.h"
18#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
19#include "chrome/browser/ui/website_settings/permission_bubble_request.h"
20#include "chrome/common/pref_names.h"
21#include "chrome/grit/generated_resources.h"
22#include "components/content_settings/core/common/permission_request_id.h"
23#include "content/public/browser/browser_thread.h"
24#include "content/public/browser/render_process_host.h"
25#include "content/public/browser/render_view_host.h"
26#include "content/public/browser/web_contents.h"
27#include "grit/theme_resources.h"
28#include "net/base/net_util.h"
29#include "ui/base/l10n/l10n_util.h"
30
31class GeolocationPermissionRequest : public PermissionBubbleRequest {
32 public:
33  GeolocationPermissionRequest(GeolocationPermissionContext* context,
34                               const PermissionRequestID& id,
35                               const GURL& requesting_frame,
36                               const GURL& embedder,
37                               bool user_gesture,
38                               base::Callback<void(bool)> callback,
39                               const std::string& display_languages);
40  virtual ~GeolocationPermissionRequest();
41
42  // PermissionBubbleDelegate:
43  virtual int GetIconID() const OVERRIDE;
44  virtual base::string16 GetMessageText() const OVERRIDE;
45  virtual base::string16 GetMessageTextFragment() const OVERRIDE;
46  virtual bool HasUserGesture() const OVERRIDE;
47  virtual GURL GetRequestingHostname() const OVERRIDE;
48  virtual void PermissionGranted() OVERRIDE;
49  virtual void PermissionDenied() OVERRIDE;
50  virtual void Cancelled() OVERRIDE;
51  virtual void RequestFinished() OVERRIDE;
52
53 private:
54  GeolocationPermissionContext* context_;
55  PermissionRequestID id_;
56  GURL requesting_frame_;
57  GURL embedder_;
58  bool user_gesture_;
59  base::Callback<void(bool)> callback_;
60  std::string display_languages_;
61};
62
63GeolocationPermissionRequest::GeolocationPermissionRequest(
64    GeolocationPermissionContext* context,
65    const PermissionRequestID& id,
66    const GURL& requesting_frame,
67    const GURL& embedder,
68    bool user_gesture,
69    base::Callback<void(bool)> callback,
70    const std::string& display_languages)
71    : context_(context),
72      id_(id),
73      requesting_frame_(requesting_frame),
74      embedder_(embedder),
75      user_gesture_(user_gesture),
76      callback_(callback),
77      display_languages_(display_languages) {}
78
79GeolocationPermissionRequest::~GeolocationPermissionRequest() {}
80
81int GeolocationPermissionRequest::GetIconID() const {
82  return IDR_INFOBAR_GEOLOCATION;
83}
84
85base::string16 GeolocationPermissionRequest::GetMessageText() const {
86  return l10n_util::GetStringFUTF16(
87      IDS_GEOLOCATION_INFOBAR_QUESTION,
88      net::FormatUrl(requesting_frame_.GetOrigin(), display_languages_,
89                     net::kFormatUrlOmitUsernamePassword |
90                     net::kFormatUrlOmitTrailingSlashOnBareHostname,
91                     net::UnescapeRule::SPACES, NULL, NULL, NULL));
92}
93
94base::string16 GeolocationPermissionRequest::GetMessageTextFragment() const {
95  return l10n_util::GetStringUTF16(IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT);
96}
97
98bool GeolocationPermissionRequest::HasUserGesture() const {
99  return user_gesture_;
100}
101
102GURL GeolocationPermissionRequest::GetRequestingHostname() const {
103  return requesting_frame_;
104}
105
106void GeolocationPermissionRequest::PermissionGranted() {
107  context_->QueueController()->UpdateContentSetting(
108      requesting_frame_, embedder_, true);
109  context_->NotifyPermissionSet(id_, requesting_frame_, callback_, true);
110}
111
112void GeolocationPermissionRequest::PermissionDenied() {
113  context_->QueueController()->UpdateContentSetting(
114      requesting_frame_, embedder_, false);
115  context_->NotifyPermissionSet(id_, requesting_frame_, callback_, false);
116}
117
118void GeolocationPermissionRequest::Cancelled() {
119}
120
121void GeolocationPermissionRequest::RequestFinished() {
122  // Deletes 'this'.
123  context_->RequestFinished(this);
124}
125
126
127GeolocationPermissionContext::GeolocationPermissionContext(
128    Profile* profile)
129    : profile_(profile),
130      shutting_down_(false),
131      extensions_context_(profile) {
132}
133
134GeolocationPermissionContext::~GeolocationPermissionContext() {
135  // GeolocationPermissionContext may be destroyed on either the UI thread
136  // or the IO thread, but the PermissionQueueController must have been
137  // destroyed on the UI thread.
138  DCHECK(!permission_queue_controller_.get());
139}
140
141void GeolocationPermissionContext::RequestGeolocationPermission(
142    content::WebContents* web_contents,
143    int bridge_id,
144    const GURL& requesting_frame,
145    bool user_gesture,
146    base::Callback<void(bool)> result_callback,
147    base::Closure* cancel_callback) {
148  GURL requesting_frame_origin = requesting_frame.GetOrigin();
149  if (shutting_down_)
150    return;
151
152  int render_process_id = web_contents->GetRenderProcessHost()->GetID();
153  int render_view_id = web_contents->GetRenderViewHost()->GetRoutingID();
154  if (cancel_callback) {
155    *cancel_callback = base::Bind(
156        &GeolocationPermissionContext::CancelGeolocationPermissionRequest,
157        this, render_process_id, render_view_id, bridge_id);
158  }
159
160  const PermissionRequestID id(
161      render_process_id, render_view_id, bridge_id, GURL());
162
163  bool permission_set;
164  bool new_permission;
165  if (extensions_context_.RequestPermission(
166          web_contents, id, bridge_id, requesting_frame, user_gesture,
167          result_callback, &permission_set, &new_permission)) {
168    if (permission_set) {
169      NotifyPermissionSet(id, requesting_frame_origin, result_callback,
170                          new_permission);
171    }
172    return;
173  }
174
175  GURL embedder = web_contents->GetLastCommittedURL().GetOrigin();
176  if (!requesting_frame_origin.is_valid() || !embedder.is_valid()) {
177    LOG(WARNING) << "Attempt to use geolocation from an invalid URL: "
178                 << requesting_frame_origin << "," << embedder
179                 << " (geolocation is not supported in popups)";
180    NotifyPermissionSet(id, requesting_frame_origin, result_callback, false);
181    return;
182  }
183
184  DecidePermission(web_contents, id, requesting_frame_origin, user_gesture,
185                   embedder, result_callback);
186}
187
188void GeolocationPermissionContext::CancelGeolocationPermissionRequest(
189    int render_process_id,
190    int render_view_id,
191    int bridge_id) {
192  content::WebContents* web_contents = tab_util::GetWebContentsByID(
193        render_process_id, render_view_id);
194  if (extensions_context_.CancelPermissionRequest(web_contents, bridge_id))
195    return;
196
197  CancelPendingInfobarRequest(PermissionRequestID(
198      render_process_id, render_view_id, bridge_id, GURL()));
199}
200
201void GeolocationPermissionContext::DecidePermission(
202    content::WebContents* web_contents,
203    const PermissionRequestID& id,
204    const GURL& requesting_frame,
205    bool user_gesture,
206    const GURL& embedder,
207    base::Callback<void(bool)> callback) {
208  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
209
210  ContentSetting content_setting =
211      profile_->GetHostContentSettingsMap()
212          ->GetContentSettingAndMaybeUpdateLastUsage(
213              requesting_frame,
214              embedder,
215              CONTENT_SETTINGS_TYPE_GEOLOCATION,
216              std::string());
217  switch (content_setting) {
218    case CONTENT_SETTING_BLOCK:
219      PermissionDecided(id, requesting_frame, embedder, callback, false);
220      break;
221    case CONTENT_SETTING_ALLOW:
222      PermissionDecided(id, requesting_frame, embedder, callback, true);
223      break;
224    default:
225      if (PermissionBubbleManager::Enabled()) {
226        PermissionBubbleManager* mgr =
227            PermissionBubbleManager::FromWebContents(web_contents);
228        if (mgr) {
229          scoped_ptr<GeolocationPermissionRequest> request_ptr(
230              new GeolocationPermissionRequest(
231                  this, id, requesting_frame, embedder, user_gesture, callback,
232                  profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)));
233          GeolocationPermissionRequest* request = request_ptr.get();
234          pending_requests_.add(id.ToString(), request_ptr.Pass());
235          mgr->AddRequest(request);
236        }
237      } else {
238        // setting == ask. Prompt the user.
239        QueueController()->CreateInfoBarRequest(
240            id, requesting_frame, embedder,
241                base::Bind(
242                    &GeolocationPermissionContext::NotifyPermissionSet,
243                base::Unretained(this), id, requesting_frame, callback));
244      }
245  }
246}
247
248void GeolocationPermissionContext::CreateInfoBarRequest(
249    const PermissionRequestID& id,
250    const GURL& requesting_frame,
251    const GURL& embedder,
252    base::Callback<void(bool)> callback) {
253    QueueController()->CreateInfoBarRequest(
254        id, requesting_frame, embedder, base::Bind(
255            &GeolocationPermissionContext::NotifyPermissionSet,
256            base::Unretained(this), id, requesting_frame, callback));
257}
258
259void GeolocationPermissionContext::RequestFinished(
260    GeolocationPermissionRequest* request) {
261  base::ScopedPtrHashMap<std::string,
262                         GeolocationPermissionRequest>::iterator it;
263  for (it = pending_requests_.begin(); it != pending_requests_.end(); ++it) {
264    if (it->second == request) {
265      pending_requests_.take_and_erase(it);
266      return;
267    }
268  }
269}
270
271
272void GeolocationPermissionContext::ShutdownOnUIThread() {
273  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
274  permission_queue_controller_.reset();
275  shutting_down_ = true;
276}
277
278void GeolocationPermissionContext::PermissionDecided(
279    const PermissionRequestID& id,
280    const GURL& requesting_frame,
281    const GURL& embedder,
282    base::Callback<void(bool)> callback,
283    bool allowed) {
284  NotifyPermissionSet(id, requesting_frame, callback, allowed);
285}
286
287void GeolocationPermissionContext::NotifyPermissionSet(
288    const PermissionRequestID& id,
289    const GURL& requesting_frame,
290    base::Callback<void(bool)> callback,
291    bool allowed) {
292  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
293
294  // WebContents may have gone away (or not exists for extension).
295  TabSpecificContentSettings* content_settings =
296      TabSpecificContentSettings::Get(id.render_process_id(),
297                                      id.render_view_id());
298  if (content_settings) {
299    content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(),
300                                                 allowed);
301  }
302
303  callback.Run(allowed);
304}
305
306PermissionQueueController*
307    GeolocationPermissionContext::QueueController() {
308  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
309  DCHECK(!shutting_down_);
310  if (!permission_queue_controller_)
311    permission_queue_controller_.reset(CreateQueueController());
312  return permission_queue_controller_.get();
313}
314
315PermissionQueueController*
316    GeolocationPermissionContext::CreateQueueController() {
317  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
318  return new PermissionQueueController(profile(),
319                                       CONTENT_SETTINGS_TYPE_GEOLOCATION);
320}
321
322void GeolocationPermissionContext::CancelPendingInfobarRequest(
323    const PermissionRequestID& id) {
324  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
325  if (shutting_down_)
326    return;
327
328  if (PermissionBubbleManager::Enabled()) {
329    GeolocationPermissionRequest* cancelling =
330        pending_requests_.get(id.ToString());
331    content::WebContents* web_contents = tab_util::GetWebContentsByID(
332        id.render_process_id(), id.render_view_id());
333    if (cancelling != NULL && web_contents != NULL &&
334        PermissionBubbleManager::FromWebContents(web_contents) != NULL) {
335      PermissionBubbleManager::FromWebContents(web_contents)->
336          CancelRequest(cancelling);
337    }
338    return;
339  }
340
341  QueueController()->CancelInfoBarRequest(id);
342}
343