blob: 62df3c091e48123fd9286229f8fc85296ce4a6ba [file] [log] [blame]
/*
* 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 APPLE COMPUTER, INC. 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 "DOMWindow.h"
#include "Frame.h"
#include "Geolocation.h"
#include "Navigator.h"
#include "SQLiteDatabase.h"
#include "SQLiteStatement.h"
#include "SQLiteTransaction.h"
#include "WebViewCore.h"
using namespace WebCore;
namespace android {
GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions;
GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances;
bool GeolocationPermissions::s_alwaysDeny = false;
String GeolocationPermissions::s_databasePath;
bool GeolocationPermissions::s_permanentPermissionsLoaded = false;
static const char* databaseName = "/GeolocationPermissions.db";
GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore, Frame* mainFrame)
: m_webViewCore(webViewCore)
, m_mainFrame(mainFrame)
, 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 (m_originInProgress.isEmpty()) {
m_originInProgress = originString;
// 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(m_originInProgress);
return;
}
// If the request in progress is not for this origin, queue this request.
if ((m_originInProgress != originString)
&& (m_queuedOrigins.find(originString) == WTF::notFound))
m_queuedOrigins.append(originString);
}
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 != m_originInProgress)
return;
maybeCallbackFrames(m_originInProgress, 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(m_originInProgress);
// Clear the origin in progress to signal that this request is done.
m_originInProgress = "";
// If there are other requests queued, start the next one.
if (!m_queuedOrigins.isEmpty()) {
m_originInProgress = m_queuedOrigins.first();
m_queuedOrigins.remove(0);
m_webViewCore->geolocationPermissionsShowPrompt(m_originInProgress);
}
}
void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember)
{
if (remember) {
s_permanentPermissions.set(m_originInProgress, allow);
} 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(m_originInProgress, 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);
if (index != WTF::notFound) {
// Get the permission from the permanent list.
PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin);
#ifndef NDEBUG
PermissionsMap::const_iterator end = s_permanentPermissions.end();
ASSERT(iter != end);
#endif
bool allow = iter->second;
maybeCallbackFrames(origin, allow);
m_queuedOrigins.remove(index);
}
}
void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer)
{
ASSERT_UNUSED(timer, timer == &m_timer);
maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow);
}
void GeolocationPermissions::resetTemporaryPermissionStates()
{
ASSERT(s_permanentPermissionsLoaded);
m_originInProgress = "";
m_queuedOrigins.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();
}
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_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);
}
void GeolocationPermissions::allow(String origin)
{
maybeLoadPermanentPermissions();
// We replace any existing permanent permission.
s_permanentPermissions.set(origin, true);
}
void GeolocationPermissions::clearAll()
{
maybeLoadPermanentPermissions();
s_permanentPermissions.clear();
}
void GeolocationPermissions::maybeLoadPermanentPermissions()
{
if (s_permanentPermissionsLoaded)
return;
s_permanentPermissionsLoaded = true;
SQLiteDatabase database;
if (!database.open(s_databasePath + databaseName))
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()
{
// Protect against the case where we haven't yet loaded permissions, as
// saving in this case would overwrite the stored permissions with the empty
// set. This is safe as the permissions are always loaded before they are
// modified.
if (!s_permanentPermissionsLoaded)
return;
SQLiteDatabase database;
if (!database.open(s_databasePath + databaseName))
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();
}
void GeolocationPermissions::setDatabasePath(String path)
{
// Take the first non-empty value.
if (s_databasePath.length() > 0)
return;
s_databasePath = path;
}
void GeolocationPermissions::setAlwaysDeny(bool deny)
{
s_alwaysDeny = deny;
}
} // namespace android