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