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