• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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