Home | History | Annotate | Download | only in WebCoreSupport
      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 
     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 
     65 GeolocationPermissions::~GeolocationPermissions()
     66 {
     67     size_t index = s_instances.find(this);
     68     s_instances.remove(index);
     69 }
     70 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    257 const WTF::String& GeolocationPermissions::nextOriginInQueue()
    258 {
    259     static const String emptyString = "";
    260     return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0];
    261 }
    262 
    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 
    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 
    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 
    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 
    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 
    319 void GeolocationPermissions::clearAll()
    320 {
    321     maybeLoadPermanentPermissions();
    322     s_permanentPermissions.clear();
    323     s_permanentPermissionsModified = true;
    324 }
    325 
    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 
    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 
    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 
    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 
    414 void GeolocationPermissions::setAlwaysDeny(bool deny)
    415 {
    416     s_alwaysDeny = deny;
    417 }
    418 
    419 }  // namespace android
    420