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/geolocation/chrome_geolocation_permission_context.h"
6
7#include <functional>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/content_settings/host_content_settings_map.h"
14#include "chrome/browser/content_settings/permission_request_id.h"
15#include "chrome/browser/content_settings/tab_specific_content_settings.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/extensions/extension_system.h"
18#include "chrome/browser/extensions/suggest_permission_util.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/tab_contents/tab_util.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/render_view_host.h"
23#include "content/public/browser/web_contents.h"
24#include "extensions/browser/view_type_utils.h"
25#include "extensions/common/extension.h"
26
27using extensions::APIPermission;
28
29ChromeGeolocationPermissionContext::ChromeGeolocationPermissionContext(
30    Profile* profile)
31    : profile_(profile),
32      shutting_down_(false) {
33}
34
35ChromeGeolocationPermissionContext::~ChromeGeolocationPermissionContext() {
36  // ChromeGeolocationPermissionContext may be destroyed on either the UI thread
37  // or the IO thread, but the PermissionQueueController must have been
38  // destroyed on the UI thread.
39  DCHECK(!permission_queue_controller_.get());
40}
41
42void ChromeGeolocationPermissionContext::RequestGeolocationPermission(
43    int render_process_id,
44    int render_view_id,
45    int bridge_id,
46    const GURL& requesting_frame,
47    base::Callback<void(bool)> callback) {
48  GURL requesting_frame_origin = requesting_frame.GetOrigin();
49  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
50    content::BrowserThread::PostTask(
51        content::BrowserThread::UI, FROM_HERE,
52        base::Bind(
53            &ChromeGeolocationPermissionContext::RequestGeolocationPermission,
54            this, render_process_id, render_view_id, bridge_id,
55            requesting_frame_origin, callback));
56    return;
57  }
58
59  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
60  if (shutting_down_)
61    return;
62
63  content::WebContents* web_contents =
64      tab_util::GetWebContentsByID(render_process_id, render_view_id);
65  const PermissionRequestID id(render_process_id, render_view_id,
66                                          bridge_id);
67  ExtensionService* extension_service =
68      extensions::ExtensionSystem::Get(profile_)->extension_service();
69  if (extension_service) {
70    const extensions::Extension* extension =
71        extension_service->extensions()->GetExtensionOrAppByURL(
72            requesting_frame_origin);
73    if (IsExtensionWithPermissionOrSuggestInConsole(APIPermission::kGeolocation,
74                                                    extension,
75                                                    profile_)) {
76      // Make sure the extension is in the calling process.
77      if (extension_service->process_map()->Contains(extension->id(),
78                                                     id.render_process_id())) {
79        NotifyPermissionSet(id, requesting_frame_origin, callback, true);
80        return;
81      }
82    }
83  }
84
85  if (extensions::GetViewType(web_contents) !=
86      extensions::VIEW_TYPE_TAB_CONTENTS) {
87    // The tab may have gone away, or the request may not be from a tab at all.
88    // TODO(mpcomplete): the request could be from a background page or
89    // extension popup (web_contents will have a different ViewType). But why do
90    // we care? Shouldn't we still put an infobar up in the current tab?
91    LOG(WARNING) << "Attempt to use geolocation tabless renderer: "
92                 << id.ToString()
93                 << " (can't prompt user without a visible tab)";
94    NotifyPermissionSet(id, requesting_frame_origin, callback, false);
95    return;
96  }
97
98  GURL embedder = web_contents->GetLastCommittedURL().GetOrigin();
99  if (!requesting_frame_origin.is_valid() || !embedder.is_valid()) {
100    LOG(WARNING) << "Attempt to use geolocation from an invalid URL: "
101                 << requesting_frame_origin << "," << embedder
102                 << " (geolocation is not supported in popups)";
103    NotifyPermissionSet(id, requesting_frame_origin, callback, false);
104    return;
105  }
106
107  DecidePermission(id, requesting_frame_origin, embedder, callback);
108}
109
110void ChromeGeolocationPermissionContext::CancelGeolocationPermissionRequest(
111    int render_process_id,
112    int render_view_id,
113    int bridge_id,
114    const GURL& requesting_frame) {
115  CancelPendingInfoBarRequest(PermissionRequestID(
116      render_process_id, render_view_id, bridge_id));
117}
118
119void ChromeGeolocationPermissionContext::DecidePermission(
120    const PermissionRequestID& id,
121    const GURL& requesting_frame,
122    const GURL& embedder,
123    base::Callback<void(bool)> callback) {
124  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
125
126  ContentSetting content_setting =
127     profile_->GetHostContentSettingsMap()->GetContentSetting(
128          requesting_frame, embedder, CONTENT_SETTINGS_TYPE_GEOLOCATION,
129          std::string());
130  switch (content_setting) {
131    case CONTENT_SETTING_BLOCK:
132      PermissionDecided(id, requesting_frame, embedder, callback, false);
133      break;
134    case CONTENT_SETTING_ALLOW:
135      PermissionDecided(id, requesting_frame, embedder, callback, true);
136      break;
137    default:
138      // setting == ask. Prompt the user.
139      QueueController()->CreateInfoBarRequest(
140          id, requesting_frame, embedder, base::Bind(
141              &ChromeGeolocationPermissionContext::NotifyPermissionSet,
142              base::Unretained(this), id, requesting_frame, callback));
143  }
144}
145
146void ChromeGeolocationPermissionContext::ShutdownOnUIThread() {
147  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
148  permission_queue_controller_.reset();
149  shutting_down_ = true;
150}
151
152void ChromeGeolocationPermissionContext::PermissionDecided(
153    const PermissionRequestID& id,
154    const GURL& requesting_frame,
155    const GURL& embedder,
156    base::Callback<void(bool)> callback,
157    bool allowed) {
158  NotifyPermissionSet(id, requesting_frame, callback, allowed);
159}
160
161void ChromeGeolocationPermissionContext::NotifyPermissionSet(
162    const PermissionRequestID& id,
163    const GURL& requesting_frame,
164    base::Callback<void(bool)> callback,
165    bool allowed) {
166  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
167
168  // WebContents may have gone away (or not exists for extension).
169  TabSpecificContentSettings* content_settings =
170      TabSpecificContentSettings::Get(id.render_process_id(),
171                                      id.render_view_id());
172  if (content_settings) {
173    content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(),
174                                                 allowed);
175  }
176
177  callback.Run(allowed);
178}
179
180PermissionQueueController*
181    ChromeGeolocationPermissionContext::QueueController() {
182  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
183  DCHECK(!shutting_down_);
184  if (!permission_queue_controller_)
185    permission_queue_controller_.reset(CreateQueueController());
186  return permission_queue_controller_.get();
187}
188
189PermissionQueueController*
190    ChromeGeolocationPermissionContext::CreateQueueController() {
191  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
192  return new PermissionQueueController(profile(),
193                                       CONTENT_SETTINGS_TYPE_GEOLOCATION);
194}
195
196void ChromeGeolocationPermissionContext::CancelPendingInfoBarRequest(
197    const PermissionRequestID& id) {
198  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
199    content::BrowserThread::PostTask(
200        content::BrowserThread::UI, FROM_HERE,
201        base::Bind(
202            &ChromeGeolocationPermissionContext::CancelPendingInfoBarRequest,
203            this, id));
204     return;
205  }
206  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
207  if (shutting_down_)
208    return;
209  QueueController()->CancelInfoBarRequest(id);
210}
211