1 /*
2 * Copyright 2009, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "GeolocationPermissions.h"
28
29 #include "DOMWindow.h"
30 #include "Frame.h"
31 #include "Geolocation.h"
32 #include "Navigator.h"
33 #include "SQLiteDatabase.h"
34 #include "SQLiteStatement.h"
35 #include "SQLiteTransaction.h"
36 #include "WebViewCore.h"
37
38 using namespace WebCore;
39
40 namespace android {
41
42 GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions;
43 GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances;
44 bool GeolocationPermissions::s_alwaysDeny = false;
45 String GeolocationPermissions::s_databasePath;
46 bool GeolocationPermissions::s_permanentPermissionsLoaded = false;
47
48 static const char* databaseName = "/GeolocationPermissions.db";
49
GeolocationPermissions(WebViewCore * webViewCore,Frame * mainFrame)50 GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore, Frame* mainFrame)
51 : m_webViewCore(webViewCore)
52 , m_mainFrame(mainFrame)
53 , m_timer(this, &GeolocationPermissions::timerFired)
54
55 {
56 ASSERT(m_webViewCore);
57 maybeLoadPermanentPermissions();
58 s_instances.append(this);
59 }
60
~GeolocationPermissions()61 GeolocationPermissions::~GeolocationPermissions()
62 {
63 size_t index = s_instances.find(this);
64 s_instances.remove(index);
65 }
66
queryPermissionState(Frame * frame)67 void GeolocationPermissions::queryPermissionState(Frame* frame)
68 {
69 ASSERT(s_permanentPermissionsLoaded);
70
71 // We use SecurityOrigin::toString to key the map. Note that testing
72 // the SecurityOrigin pointer for equality is insufficient.
73 String originString = frame->document()->securityOrigin()->toString();
74
75 // If we've been told to always deny requests, do so.
76 if (s_alwaysDeny) {
77 makeAsynchronousCallbackToGeolocation(originString, false);
78 return;
79 }
80
81 // See if we have a record for this origin in the permanent permissions.
82 // These take precedence over temporary permissions so that changes made
83 // from the browser settings work as intended.
84 PermissionsMap::const_iterator iter = s_permanentPermissions.find(originString);
85 PermissionsMap::const_iterator end = s_permanentPermissions.end();
86 if (iter != end) {
87 bool allow = iter->second;
88 makeAsynchronousCallbackToGeolocation(originString, allow);
89 return;
90 }
91
92 // Check the temporary permisions.
93 iter = m_temporaryPermissions.find(originString);
94 end = m_temporaryPermissions.end();
95 if (iter != end) {
96 bool allow = iter->second;
97 makeAsynchronousCallbackToGeolocation(originString, allow);
98 return;
99 }
100
101 // If there's no pending request, prompt the user.
102 if (m_originInProgress.isEmpty()) {
103 m_originInProgress = originString;
104
105 // Although multiple tabs may request permissions for the same origin
106 // simultaneously, the routing in WebViewCore/CallbackProxy ensures that
107 // the result of the request will make it back to this object, so
108 // there's no need for a globally unique ID for the request.
109 m_webViewCore->geolocationPermissionsShowPrompt(m_originInProgress);
110 return;
111 }
112
113 // If the request in progress is not for this origin, queue this request.
114 if ((m_originInProgress != originString)
115 && (m_queuedOrigins.find(originString) == WTF::notFound))
116 m_queuedOrigins.append(originString);
117 }
118
makeAsynchronousCallbackToGeolocation(String origin,bool allow)119 void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow)
120 {
121 m_callbackData.origin = origin;
122 m_callbackData.allow = allow;
123 m_timer.startOneShot(0);
124 }
125
providePermissionState(String origin,bool allow,bool remember)126 void GeolocationPermissions::providePermissionState(String origin, bool allow, bool remember)
127 {
128 ASSERT(s_permanentPermissionsLoaded);
129
130 // It's possible that this method is called with an origin that doesn't
131 // match m_originInProgress. This can occur if this object is reset
132 // while a permission result is in the process of being marshalled back to
133 // the WebCore thread from the browser. In this case, we simply ignore the
134 // call.
135 if (origin != m_originInProgress)
136 return;
137
138 maybeCallbackFrames(m_originInProgress, allow);
139 recordPermissionState(origin, allow, remember);
140
141 // If the permissions are set to be remembered, cancel any queued requests
142 // for this domain in other tabs.
143 if (remember)
144 cancelPendingRequestsInOtherTabs(m_originInProgress);
145
146 // Clear the origin in progress to signal that this request is done.
147 m_originInProgress = "";
148
149 // If there are other requests queued, start the next one.
150 if (!m_queuedOrigins.isEmpty()) {
151 m_originInProgress = m_queuedOrigins.first();
152 m_queuedOrigins.remove(0);
153 m_webViewCore->geolocationPermissionsShowPrompt(m_originInProgress);
154 }
155 }
156
recordPermissionState(String origin,bool allow,bool remember)157 void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember)
158 {
159 if (remember) {
160 s_permanentPermissions.set(m_originInProgress, allow);
161 } else {
162 // It's possible that another tab recorded a permanent permission for
163 // this origin while our request was in progress, but we record it
164 // anyway.
165 m_temporaryPermissions.set(m_originInProgress, allow);
166 }
167 }
168
cancelPendingRequestsInOtherTabs(String origin)169 void GeolocationPermissions::cancelPendingRequestsInOtherTabs(String origin)
170 {
171 for (GeolocationPermissionsVector::const_iterator iter = s_instances.begin();
172 iter != s_instances.end();
173 ++iter)
174 (*iter)->cancelPendingRequests(origin);
175 }
176
cancelPendingRequests(String origin)177 void GeolocationPermissions::cancelPendingRequests(String origin)
178 {
179 size_t index = m_queuedOrigins.find(origin);
180 if (index != WTF::notFound) {
181 // Get the permission from the permanent list.
182 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin);
183 #ifndef NDEBUG
184 PermissionsMap::const_iterator end = s_permanentPermissions.end();
185 ASSERT(iter != end);
186 #endif
187 bool allow = iter->second;
188
189 maybeCallbackFrames(origin, allow);
190
191 m_queuedOrigins.remove(index);
192 }
193 }
194
timerFired(Timer<GeolocationPermissions> * timer)195 void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer)
196 {
197 ASSERT_UNUSED(timer, timer == &m_timer);
198 maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow);
199 }
200
resetTemporaryPermissionStates()201 void GeolocationPermissions::resetTemporaryPermissionStates()
202 {
203 ASSERT(s_permanentPermissionsLoaded);
204 m_originInProgress = "";
205 m_queuedOrigins.clear();
206 m_temporaryPermissions.clear();
207 // If any permission results are being marshalled back to this thread, this
208 // will render them inefective.
209 m_timer.stop();
210
211 m_webViewCore->geolocationPermissionsHidePrompt();
212 }
213
maybeCallbackFrames(String origin,bool allow)214 void GeolocationPermissions::maybeCallbackFrames(String origin, bool allow)
215 {
216 // We can't track which frame issued the request, as frames can be deleted
217 // or have their contents replaced. Even uniqueChildName is not unique when
218 // frames are dynamically deleted and created. Instead, we simply call back
219 // to the Geolocation object in all frames from the correct origin.
220 for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) {
221 if (origin == frame->document()->securityOrigin()->toString()) {
222 // If the page has changed, it may no longer have a Geolocation
223 // object.
224 Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation();
225 if (geolocation)
226 geolocation->setIsAllowed(allow);
227 }
228 }
229 }
230
getOrigins()231 GeolocationPermissions::OriginSet GeolocationPermissions::getOrigins()
232 {
233 maybeLoadPermanentPermissions();
234 OriginSet origins;
235 PermissionsMap::const_iterator end = s_permanentPermissions.end();
236 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter)
237 origins.add(iter->first);
238 return origins;
239 }
240
getAllowed(String origin)241 bool GeolocationPermissions::getAllowed(String origin)
242 {
243 maybeLoadPermanentPermissions();
244 bool allowed = false;
245 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin);
246 PermissionsMap::const_iterator end = s_permanentPermissions.end();
247 if (iter != end)
248 allowed = iter->second;
249 return allowed;
250 }
251
clear(String origin)252 void GeolocationPermissions::clear(String origin)
253 {
254 maybeLoadPermanentPermissions();
255 PermissionsMap::iterator iter = s_permanentPermissions.find(origin);
256 if (iter != s_permanentPermissions.end())
257 s_permanentPermissions.remove(iter);
258 }
259
allow(String origin)260 void GeolocationPermissions::allow(String origin)
261 {
262 maybeLoadPermanentPermissions();
263 // We replace any existing permanent permission.
264 s_permanentPermissions.set(origin, true);
265 }
266
clearAll()267 void GeolocationPermissions::clearAll()
268 {
269 maybeLoadPermanentPermissions();
270 s_permanentPermissions.clear();
271 }
272
maybeLoadPermanentPermissions()273 void GeolocationPermissions::maybeLoadPermanentPermissions()
274 {
275 if (s_permanentPermissionsLoaded)
276 return;
277 s_permanentPermissionsLoaded = true;
278
279 SQLiteDatabase database;
280 if (!database.open(s_databasePath + databaseName))
281 return;
282
283 // Create the table here, such that even if we've just created the DB, the
284 // commands below should succeed.
285 if (!database.executeCommand("CREATE TABLE IF NOT EXISTS Permissions (origin TEXT UNIQUE NOT NULL, allow INTEGER NOT NULL)")) {
286 database.close();
287 return;
288 }
289
290 SQLiteStatement statement(database, "SELECT * FROM Permissions");
291 if (statement.prepare() != SQLResultOk) {
292 database.close();
293 return;
294 }
295
296 ASSERT(s_permanentPermissions.size() == 0);
297 while (statement.step() == SQLResultRow)
298 s_permanentPermissions.set(statement.getColumnText(0), statement.getColumnInt64(1));
299
300 database.close();
301 }
302
maybeStorePermanentPermissions()303 void GeolocationPermissions::maybeStorePermanentPermissions()
304 {
305 // Protect against the case where we haven't yet loaded permissions, as
306 // saving in this case would overwrite the stored permissions with the empty
307 // set. This is safe as the permissions are always loaded before they are
308 // modified.
309 if (!s_permanentPermissionsLoaded)
310 return;
311
312 SQLiteDatabase database;
313 if (!database.open(s_databasePath + databaseName))
314 return;
315
316 SQLiteTransaction transaction(database);
317
318 // The number of entries should be small enough that it's not worth trying
319 // to perform a diff. Simply clear the table and repopulate it.
320 if (!database.executeCommand("DELETE FROM Permissions")) {
321 database.close();
322 return;
323 }
324
325 PermissionsMap::const_iterator end = s_permanentPermissions.end();
326 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) {
327 SQLiteStatement statement(database, "INSERT INTO Permissions (origin, allow) VALUES (?, ?)");
328 if (statement.prepare() != SQLResultOk)
329 continue;
330 statement.bindText(1, iter->first);
331 statement.bindInt64(2, iter->second);
332 statement.executeCommand();
333 }
334
335 transaction.commit();
336 database.close();
337 }
338
setDatabasePath(String path)339 void GeolocationPermissions::setDatabasePath(String path)
340 {
341 // Take the first non-empty value.
342 if (s_databasePath.length() > 0)
343 return;
344 s_databasePath = path;
345 }
346
setAlwaysDeny(bool deny)347 void GeolocationPermissions::setAlwaysDeny(bool deny)
348 {
349 s_alwaysDeny = deny;
350 }
351
352 } // namespace android
353