/* * Copyright 2009, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "GeolocationPermissions.h" #include "WebViewCore.h" #include #include #include #include #include #include #include #include #include using namespace WebCore; namespace android { GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions; GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances; bool GeolocationPermissions::s_alwaysDeny = false; bool GeolocationPermissions::s_permanentPermissionsLoaded = false; bool GeolocationPermissions::s_permanentPermissionsModified = false; String GeolocationPermissions::s_databasePath; static const char* databaseName = "GeolocationPermissions.db"; GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore) : m_webViewCore(webViewCore) , m_timer(this, &GeolocationPermissions::timerFired) { ASSERT(m_webViewCore); maybeLoadPermanentPermissions(); s_instances.append(this); } GeolocationPermissions::~GeolocationPermissions() { size_t index = s_instances.find(this); s_instances.remove(index); } void GeolocationPermissions::queryPermissionState(Frame* frame) { ASSERT(s_permanentPermissionsLoaded); // We use SecurityOrigin::toString to key the map. Note that testing // the SecurityOrigin pointer for equality is insufficient. String originString = frame->document()->securityOrigin()->toString(); // If we've been told to always deny requests, do so. if (s_alwaysDeny) { makeAsynchronousCallbackToGeolocation(originString, false); return; } // See if we have a record for this origin in the permanent permissions. // These take precedence over temporary permissions so that changes made // from the browser settings work as intended. PermissionsMap::const_iterator iter = s_permanentPermissions.find(originString); PermissionsMap::const_iterator end = s_permanentPermissions.end(); if (iter != end) { bool allow = iter->second; makeAsynchronousCallbackToGeolocation(originString, allow); return; } // Check the temporary permisions. iter = m_temporaryPermissions.find(originString); end = m_temporaryPermissions.end(); if (iter != end) { bool allow = iter->second; makeAsynchronousCallbackToGeolocation(originString, allow); return; } // If there's no pending request, prompt the user. if (nextOriginInQueue().isEmpty()) { // Although multiple tabs may request permissions for the same origin // simultaneously, the routing in WebViewCore/CallbackProxy ensures that // the result of the request will make it back to this object, so // there's no need for a globally unique ID for the request. m_webViewCore->geolocationPermissionsShowPrompt(originString); } // Add this request to the queue so we can track which frames requested it. if (m_queuedOrigins.find(originString) == WTF::notFound) { m_queuedOrigins.append(originString); FrameSet frameSet; frameSet.add(frame); m_queuedOriginsToFramesMap.add(originString, frameSet); } else { ASSERT(m_queuedOriginsToFramesMap.contains(originString)); m_queuedOriginsToFramesMap.find(originString)->second.add(frame); } } void GeolocationPermissions::cancelPermissionStateQuery(WebCore::Frame* frame) { // We cancel any queued request for the given frame. There can be at most // one of these, since each frame maps to a single origin. We only cancel // the request if this frame is the only one reqesting permission for this // origin. // // We can use the origin string to avoid searching the map. String originString = frame->document()->securityOrigin()->toString(); size_t index = m_queuedOrigins.find(originString); if (index == WTF::notFound) return; ASSERT(m_queuedOriginsToFramesMap.contains(originString)); OriginToFramesMap::iterator iter = m_queuedOriginsToFramesMap.find(originString); ASSERT(iter->second.contains(frame)); iter->second.remove(frame); if (!iter->second.isEmpty()) return; m_queuedOrigins.remove(index); m_queuedOriginsToFramesMap.remove(iter); // If this is the origin currently being shown, cancel the prompt // and show the next in the queue, if present. if (index == 0) { m_webViewCore->geolocationPermissionsHidePrompt(); if (!nextOriginInQueue().isEmpty()) m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); } } void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow) { m_callbackData.origin = origin; m_callbackData.allow = allow; m_timer.startOneShot(0); } void GeolocationPermissions::providePermissionState(String origin, bool allow, bool remember) { ASSERT(s_permanentPermissionsLoaded); // It's possible that this method is called with an origin that doesn't // match m_originInProgress. This can occur if this object is reset // while a permission result is in the process of being marshalled back to // the WebCore thread from the browser. In this case, we simply ignore the // call. if (origin != nextOriginInQueue()) return; maybeCallbackFrames(origin, allow); recordPermissionState(origin, allow, remember); // If the permissions are set to be remembered, cancel any queued requests // for this domain in other tabs. if (remember) cancelPendingRequestsInOtherTabs(origin); // Clear the origin from the queue. ASSERT(!m_queuedOrigins.isEmpty()); m_queuedOrigins.remove(0); ASSERT(m_queuedOriginsToFramesMap.contains(origin)); m_queuedOriginsToFramesMap.remove(origin); // If there are other requests queued, start the next one. if (!nextOriginInQueue().isEmpty()) m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); } void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember) { if (remember) { s_permanentPermissions.set(origin, allow); s_permanentPermissionsModified = true; } else { // It's possible that another tab recorded a permanent permission for // this origin while our request was in progress, but we record it // anyway. m_temporaryPermissions.set(origin, allow); } } void GeolocationPermissions::cancelPendingRequestsInOtherTabs(String origin) { for (GeolocationPermissionsVector::const_iterator iter = s_instances.begin(); iter != s_instances.end(); ++iter) (*iter)->cancelPendingRequests(origin); } void GeolocationPermissions::cancelPendingRequests(String origin) { size_t index = m_queuedOrigins.find(origin); // Don't cancel the request if it's currently being shown, in which case // it's at index 0. if (index == WTF::notFound || !index) return; // Get the permission from the permanent list. ASSERT(s_permanentPermissions.contains(origin)); PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); bool allow = iter->second; maybeCallbackFrames(origin, allow); m_queuedOrigins.remove(index); ASSERT(m_queuedOriginsToFramesMap.contains(origin)); m_queuedOriginsToFramesMap.remove(origin); } void GeolocationPermissions::timerFired(Timer* timer) { ASSERT_UNUSED(timer, timer == &m_timer); maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow); } void GeolocationPermissions::resetTemporaryPermissionStates() { ASSERT(s_permanentPermissionsLoaded); m_queuedOrigins.clear(); m_queuedOriginsToFramesMap.clear(); m_temporaryPermissions.clear(); // If any permission results are being marshalled back to this thread, this // will render them inefective. m_timer.stop(); m_webViewCore->geolocationPermissionsHidePrompt(); } const WTF::String& GeolocationPermissions::nextOriginInQueue() { static const String emptyString = ""; return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0]; } void GeolocationPermissions::maybeCallbackFrames(String origin, bool allow) { // We can't track which frame issued the request, as frames can be deleted // or have their contents replaced. Even uniqueChildName is not unique when // frames are dynamically deleted and created. Instead, we simply call back // to the Geolocation object in all frames from the correct origin. for (Frame* frame = m_webViewCore->mainFrame(); frame; frame = frame->tree()->traverseNext()) { if (origin == frame->document()->securityOrigin()->toString()) { // If the page has changed, it may no longer have a Geolocation // object. Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); if (geolocation) geolocation->setIsAllowed(allow); } } } GeolocationPermissions::OriginSet GeolocationPermissions::getOrigins() { maybeLoadPermanentPermissions(); OriginSet origins; PermissionsMap::const_iterator end = s_permanentPermissions.end(); for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) origins.add(iter->first); return origins; } bool GeolocationPermissions::getAllowed(String origin) { maybeLoadPermanentPermissions(); bool allowed = false; PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); PermissionsMap::const_iterator end = s_permanentPermissions.end(); if (iter != end) allowed = iter->second; return allowed; } void GeolocationPermissions::clear(String origin) { maybeLoadPermanentPermissions(); PermissionsMap::iterator iter = s_permanentPermissions.find(origin); if (iter != s_permanentPermissions.end()) { s_permanentPermissions.remove(iter); s_permanentPermissionsModified = true; } } void GeolocationPermissions::allow(String origin) { maybeLoadPermanentPermissions(); // We replace any existing permanent permission. s_permanentPermissions.set(origin, true); s_permanentPermissionsModified = true; } void GeolocationPermissions::clearAll() { maybeLoadPermanentPermissions(); s_permanentPermissions.clear(); s_permanentPermissionsModified = true; } void GeolocationPermissions::maybeLoadPermanentPermissions() { if (s_permanentPermissionsLoaded) return; s_permanentPermissionsLoaded = true; SQLiteDatabase database; if (!openDatabase(&database)) return; // Create the table here, such that even if we've just created the DB, the // commands below should succeed. if (!database.executeCommand("CREATE TABLE IF NOT EXISTS Permissions (origin TEXT UNIQUE NOT NULL, allow INTEGER NOT NULL)")) { database.close(); return; } SQLiteStatement statement(database, "SELECT * FROM Permissions"); if (statement.prepare() != SQLResultOk) { database.close(); return; } ASSERT(s_permanentPermissions.size() == 0); while (statement.step() == SQLResultRow) s_permanentPermissions.set(statement.getColumnText(0), statement.getColumnInt64(1)); database.close(); } void GeolocationPermissions::maybeStorePermanentPermissions() { // If the permanent permissions haven't been modified, there's no need to // save them to the DB. (If we haven't even loaded them, writing them now // would overwrite the stored permissions with the empty set.) if (!s_permanentPermissionsModified) return; SQLiteDatabase database; if (!openDatabase(&database)) return; SQLiteTransaction transaction(database); // The number of entries should be small enough that it's not worth trying // to perform a diff. Simply clear the table and repopulate it. if (!database.executeCommand("DELETE FROM Permissions")) { database.close(); return; } PermissionsMap::const_iterator end = s_permanentPermissions.end(); for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) { SQLiteStatement statement(database, "INSERT INTO Permissions (origin, allow) VALUES (?, ?)"); if (statement.prepare() != SQLResultOk) continue; statement.bindText(1, iter->first); statement.bindInt64(2, iter->second); statement.executeCommand(); } transaction.commit(); database.close(); s_permanentPermissionsModified = false; } void GeolocationPermissions::setDatabasePath(String path) { // Take the first non-empty value. if (s_databasePath.length() > 0) return; s_databasePath = path; } bool GeolocationPermissions::openDatabase(SQLiteDatabase* database) { ASSERT(database); String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(s_databasePath, databaseName); if (!database->open(filename)) return false; if (chmod(filename.utf8().data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) { database->close(); return false; } return true; } void GeolocationPermissions::setAlwaysDeny(bool deny) { s_alwaysDeny = deny; } } // namespace android