• 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 "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