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 THE COPYRIGHT OWNER 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 "CString.h"
30 #include "DOMWindow.h"
31 #include "Frame.h"
32 #include "Geolocation.h"
33 #include "Navigator.h"
34 #include "SQLiteDatabase.h"
35 #include "SQLiteFileSystem.h"
36 #include "SQLiteStatement.h"
37 #include "SQLiteTransaction.h"
38 #include "WebViewCore.h"
39
40 using namespace WebCore;
41
42 namespace android {
43
44 GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions;
45 GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances;
46 bool GeolocationPermissions::s_alwaysDeny = false;
47 bool GeolocationPermissions::s_permanentPermissionsLoaded = false;
48 bool GeolocationPermissions::s_permanentPermissionsModified = false;
49 String GeolocationPermissions::s_databasePath;
50
51 static const char* databaseName = "GeolocationPermissions.db";
52
GeolocationPermissions(WebViewCore * webViewCore,Frame * mainFrame)53 GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore, Frame* mainFrame)
54 : m_webViewCore(webViewCore)
55 , m_mainFrame(mainFrame)
56 , m_timer(this, &GeolocationPermissions::timerFired)
57
58 {
59 ASSERT(m_webViewCore);
60 maybeLoadPermanentPermissions();
61 s_instances.append(this);
62 }
63
~GeolocationPermissions()64 GeolocationPermissions::~GeolocationPermissions()
65 {
66 size_t index = s_instances.find(this);
67 s_instances.remove(index);
68 }
69
queryPermissionState(Frame * frame)70 void GeolocationPermissions::queryPermissionState(Frame* frame)
71 {
72 ASSERT(s_permanentPermissionsLoaded);
73
74 // We use SecurityOrigin::toString to key the map. Note that testing
75 // the SecurityOrigin pointer for equality is insufficient.
76 String originString = frame->document()->securityOrigin()->toString();
77
78 // If we've been told to always deny requests, do so.
79 if (s_alwaysDeny) {
80 makeAsynchronousCallbackToGeolocation(originString, false);
81 return;
82 }
83
84 // See if we have a record for this origin in the permanent permissions.
85 // These take precedence over temporary permissions so that changes made
86 // from the browser settings work as intended.
87 PermissionsMap::const_iterator iter = s_permanentPermissions.find(originString);
88 PermissionsMap::const_iterator end = s_permanentPermissions.end();
89 if (iter != end) {
90 bool allow = iter->second;
91 makeAsynchronousCallbackToGeolocation(originString, allow);
92 return;
93 }
94
95 // Check the temporary permisions.
96 iter = m_temporaryPermissions.find(originString);
97 end = m_temporaryPermissions.end();
98 if (iter != end) {
99 bool allow = iter->second;
100 makeAsynchronousCallbackToGeolocation(originString, allow);
101 return;
102 }
103
104 // If there's no pending request, prompt the user.
105 if (nextOriginInQueue().isEmpty()) {
106 // Although multiple tabs may request permissions for the same origin
107 // simultaneously, the routing in WebViewCore/CallbackProxy ensures that
108 // the result of the request will make it back to this object, so
109 // there's no need for a globally unique ID for the request.
110 m_webViewCore->geolocationPermissionsShowPrompt(originString);
111 }
112
113 // Add this request to the queue so we can track which frames requested it.
114 if (m_queuedOrigins.find(originString) == WTF::notFound) {
115 m_queuedOrigins.append(originString);
116 FrameSet frameSet;
117 frameSet.add(frame);
118 m_queuedOriginsToFramesMap.add(originString, frameSet);
119 } else {
120 ASSERT(m_queuedOriginsToFramesMap.contains(originString));
121 m_queuedOriginsToFramesMap.find(originString)->second.add(frame);
122 }
123 }
124
cancelPermissionStateQuery(WebCore::Frame * frame)125 void GeolocationPermissions::cancelPermissionStateQuery(WebCore::Frame* frame)
126 {
127 // We cancel any queued request for the given frame. There can be at most
128 // one of these, since each frame maps to a single origin. We only cancel
129 // the request if this frame is the only one reqesting permission for this
130 // origin.
131 //
132 // We can use the origin string to avoid searching the map.
133 String originString = frame->document()->securityOrigin()->toString();
134 size_t index = m_queuedOrigins.find(originString);
135 if (index == WTF::notFound)
136 return;
137
138 ASSERT(m_queuedOriginsToFramesMap.contains(originString));
139 OriginToFramesMap::iterator iter = m_queuedOriginsToFramesMap.find(originString);
140 ASSERT(iter->second.contains(frame));
141 iter->second.remove(frame);
142 if (!iter->second.isEmpty())
143 return;
144
145 m_queuedOrigins.remove(index);
146 m_queuedOriginsToFramesMap.remove(iter);
147
148 // If this is the origin currently being shown, cancel the prompt
149 // and show the next in the queue, if present.
150 if (index == 0) {
151 m_webViewCore->geolocationPermissionsHidePrompt();
152 if (!nextOriginInQueue().isEmpty())
153 m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue());
154 }
155 }
156
makeAsynchronousCallbackToGeolocation(String origin,bool allow)157 void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow)
158 {
159 m_callbackData.origin = origin;
160 m_callbackData.allow = allow;
161 m_timer.startOneShot(0);
162 }
163
providePermissionState(String origin,bool allow,bool remember)164 void GeolocationPermissions::providePermissionState(String origin, bool allow, bool remember)
165 {
166 ASSERT(s_permanentPermissionsLoaded);
167
168 // It's possible that this method is called with an origin that doesn't
169 // match m_originInProgress. This can occur if this object is reset
170 // while a permission result is in the process of being marshalled back to
171 // the WebCore thread from the browser. In this case, we simply ignore the
172 // call.
173 if (origin != nextOriginInQueue())
174 return;
175
176 maybeCallbackFrames(origin, allow);
177 recordPermissionState(origin, allow, remember);
178
179 // If the permissions are set to be remembered, cancel any queued requests
180 // for this domain in other tabs.
181 if (remember)
182 cancelPendingRequestsInOtherTabs(origin);
183
184 // Clear the origin from the queue.
185 ASSERT(!m_queuedOrigins.isEmpty());
186 m_queuedOrigins.remove(0);
187 ASSERT(m_queuedOriginsToFramesMap.contains(origin));
188 m_queuedOriginsToFramesMap.remove(origin);
189
190 // If there are other requests queued, start the next one.
191 if (!nextOriginInQueue().isEmpty())
192 m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue());
193 }
194
recordPermissionState(String origin,bool allow,bool remember)195 void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember)
196 {
197 if (remember) {
198 s_permanentPermissions.set(origin, allow);
199 s_permanentPermissionsModified = true;
200 } else {
201 // It's possible that another tab recorded a permanent permission for
202 // this origin while our request was in progress, but we record it
203 // anyway.
204 m_temporaryPermissions.set(origin, allow);
205 }
206 }
207
cancelPendingRequestsInOtherTabs(String origin)208 void GeolocationPermissions::cancelPendingRequestsInOtherTabs(String origin)
209 {
210 for (GeolocationPermissionsVector::const_iterator iter = s_instances.begin();
211 iter != s_instances.end();
212 ++iter)
213 (*iter)->cancelPendingRequests(origin);
214 }
215
cancelPendingRequests(String origin)216 void GeolocationPermissions::cancelPendingRequests(String origin)
217 {
218 size_t index = m_queuedOrigins.find(origin);
219
220 // Don't cancel the request if it's currently being shown, in which case
221 // it's at index 0.
222 if (index == WTF::notFound || !index)
223 return;
224
225 // Get the permission from the permanent list.
226 ASSERT(s_permanentPermissions.contains(origin));
227 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin);
228 bool allow = iter->second;
229
230 maybeCallbackFrames(origin, allow);
231
232 m_queuedOrigins.remove(index);
233 ASSERT(m_queuedOriginsToFramesMap.contains(origin));
234 m_queuedOriginsToFramesMap.remove(origin);
235 }
236
timerFired(Timer<GeolocationPermissions> * timer)237 void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer)
238 {
239 ASSERT_UNUSED(timer, timer == &m_timer);
240 maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow);
241 }
242
resetTemporaryPermissionStates()243 void GeolocationPermissions::resetTemporaryPermissionStates()
244 {
245 ASSERT(s_permanentPermissionsLoaded);
246 m_queuedOrigins.clear();
247 m_queuedOriginsToFramesMap.clear();
248 m_temporaryPermissions.clear();
249 // If any permission results are being marshalled back to this thread, this
250 // will render them inefective.
251 m_timer.stop();
252
253 m_webViewCore->geolocationPermissionsHidePrompt();
254 }
255
nextOriginInQueue()256 const WebCore::String& GeolocationPermissions::nextOriginInQueue()
257 {
258 static const String emptyString = "";
259 return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0];
260 }
261
maybeCallbackFrames(String origin,bool allow)262 void GeolocationPermissions::maybeCallbackFrames(String origin, bool allow)
263 {
264 // We can't track which frame issued the request, as frames can be deleted
265 // or have their contents replaced. Even uniqueChildName is not unique when
266 // frames are dynamically deleted and created. Instead, we simply call back
267 // to the Geolocation object in all frames from the correct origin.
268 for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) {
269 if (origin == frame->document()->securityOrigin()->toString()) {
270 // If the page has changed, it may no longer have a Geolocation
271 // object.
272 Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation();
273 if (geolocation)
274 geolocation->setIsAllowed(allow);
275 }
276 }
277 }
278
getOrigins()279 GeolocationPermissions::OriginSet GeolocationPermissions::getOrigins()
280 {
281 maybeLoadPermanentPermissions();
282 OriginSet origins;
283 PermissionsMap::const_iterator end = s_permanentPermissions.end();
284 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter)
285 origins.add(iter->first);
286 return origins;
287 }
288
getAllowed(String origin)289 bool GeolocationPermissions::getAllowed(String origin)
290 {
291 maybeLoadPermanentPermissions();
292 bool allowed = false;
293 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin);
294 PermissionsMap::const_iterator end = s_permanentPermissions.end();
295 if (iter != end)
296 allowed = iter->second;
297 return allowed;
298 }
299
clear(String origin)300 void GeolocationPermissions::clear(String origin)
301 {
302 maybeLoadPermanentPermissions();
303 PermissionsMap::iterator iter = s_permanentPermissions.find(origin);
304 if (iter != s_permanentPermissions.end()) {
305 s_permanentPermissions.remove(iter);
306 s_permanentPermissionsModified = true;
307 }
308 }
309
allow(String origin)310 void GeolocationPermissions::allow(String origin)
311 {
312 maybeLoadPermanentPermissions();
313 // We replace any existing permanent permission.
314 s_permanentPermissions.set(origin, true);
315 s_permanentPermissionsModified = true;
316 }
317
clearAll()318 void GeolocationPermissions::clearAll()
319 {
320 maybeLoadPermanentPermissions();
321 s_permanentPermissions.clear();
322 s_permanentPermissionsModified = true;
323 }
324
maybeLoadPermanentPermissions()325 void GeolocationPermissions::maybeLoadPermanentPermissions()
326 {
327 if (s_permanentPermissionsLoaded)
328 return;
329 s_permanentPermissionsLoaded = true;
330
331 SQLiteDatabase database;
332 if (!openDatabase(&database))
333 return;
334
335 // Create the table here, such that even if we've just created the DB, the
336 // commands below should succeed.
337 if (!database.executeCommand("CREATE TABLE IF NOT EXISTS Permissions (origin TEXT UNIQUE NOT NULL, allow INTEGER NOT NULL)")) {
338 database.close();
339 return;
340 }
341
342 SQLiteStatement statement(database, "SELECT * FROM Permissions");
343 if (statement.prepare() != SQLResultOk) {
344 database.close();
345 return;
346 }
347
348 ASSERT(s_permanentPermissions.size() == 0);
349 while (statement.step() == SQLResultRow)
350 s_permanentPermissions.set(statement.getColumnText(0), statement.getColumnInt64(1));
351
352 database.close();
353 }
354
maybeStorePermanentPermissions()355 void GeolocationPermissions::maybeStorePermanentPermissions()
356 {
357 // If the permanent permissions haven't been modified, there's no need to
358 // save them to the DB. (If we haven't even loaded them, writing them now
359 // would overwrite the stored permissions with the empty set.)
360 if (!s_permanentPermissionsModified)
361 return;
362
363 SQLiteDatabase database;
364 if (!openDatabase(&database))
365 return;
366
367 SQLiteTransaction transaction(database);
368
369 // The number of entries should be small enough that it's not worth trying
370 // to perform a diff. Simply clear the table and repopulate it.
371 if (!database.executeCommand("DELETE FROM Permissions")) {
372 database.close();
373 return;
374 }
375
376 PermissionsMap::const_iterator end = s_permanentPermissions.end();
377 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) {
378 SQLiteStatement statement(database, "INSERT INTO Permissions (origin, allow) VALUES (?, ?)");
379 if (statement.prepare() != SQLResultOk)
380 continue;
381 statement.bindText(1, iter->first);
382 statement.bindInt64(2, iter->second);
383 statement.executeCommand();
384 }
385
386 transaction.commit();
387 database.close();
388
389 s_permanentPermissionsModified = false;
390 }
391
setDatabasePath(String path)392 void GeolocationPermissions::setDatabasePath(String path)
393 {
394 // Take the first non-empty value.
395 if (s_databasePath.length() > 0)
396 return;
397 s_databasePath = path;
398 }
399
openDatabase(SQLiteDatabase * database)400 bool GeolocationPermissions::openDatabase(SQLiteDatabase* database)
401 {
402 ASSERT(database);
403 String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(s_databasePath, databaseName);
404 if (!database->open(filename))
405 return false;
406 if (chmod(filename.utf8().data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) {
407 database->close();
408 return false;
409 }
410 return true;
411 }
412
setAlwaysDeny(bool deny)413 void GeolocationPermissions::setAlwaysDeny(bool deny)
414 {
415 s_alwaysDeny = deny;
416 }
417
418 } // namespace android
419