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 "WebViewCore.h"
     30 
     31 #include <DOMWindow.h>
     32 #include <Frame.h>
     33 #include <Geolocation.h>
     34 #include <Navigator.h>
     35 #include <SQLiteDatabase.h>
     36 #include <SQLiteFileSystem.h>
     37 #include <SQLiteStatement.h>
     38 #include <SQLiteTransaction.h>
     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)
     55     : m_webViewCore(webViewCore)
     56     , m_timer(this, &GeolocationPermissions::timerFired)
     57 
     58 {
     59     ASSERT(m_webViewCore);
     60     maybeLoadPermanentPermissions();
     61     s_instances.append(this);
     62 }
     63 
     64 GeolocationPermissions::~GeolocationPermissions()
     65 {
     66     size_t index = s_instances.find(this);
     67     s_instances.remove(index);
     68 }
     69 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    256 const WTF::String& GeolocationPermissions::nextOriginInQueue()
    257 {
    258     static const String emptyString = "";
    259     return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0];
    260 }
    261 
    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_webViewCore->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 
    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 
    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 
    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 
    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 
    318 void GeolocationPermissions::clearAll()
    319 {
    320     maybeLoadPermanentPermissions();
    321     s_permanentPermissions.clear();
    322     s_permanentPermissionsModified = true;
    323 }
    324 
    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 
    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 
    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 
    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 
    413 void GeolocationPermissions::setAlwaysDeny(bool deny)
    414 {
    415     s_alwaysDeny = deny;
    416 }
    417 
    418 }  // namespace android
    419