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 41using namespace WebCore; 42 43namespace android { 44 45GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions; 46GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances; 47bool GeolocationPermissions::s_alwaysDeny = false; 48bool GeolocationPermissions::s_permanentPermissionsLoaded = false; 49bool GeolocationPermissions::s_permanentPermissionsModified = false; 50String GeolocationPermissions::s_databasePath; 51 52static const char* databaseName = "GeolocationPermissions.db"; 53 54GeolocationPermissions::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 65GeolocationPermissions::~GeolocationPermissions() 66{ 67 size_t index = s_instances.find(this); 68 s_instances.remove(index); 69} 70 71void 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 126void 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 158void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow) 159{ 160 m_callbackData.origin = origin; 161 m_callbackData.allow = allow; 162 m_timer.startOneShot(0); 163} 164 165void 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 196void 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 209void 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 217void 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 238void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer) 239{ 240 ASSERT_UNUSED(timer, timer == &m_timer); 241 maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow); 242} 243 244void 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 257const WTF::String& GeolocationPermissions::nextOriginInQueue() 258{ 259 static const String emptyString = ""; 260 return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0]; 261} 262 263void 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 280GeolocationPermissions::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 290bool 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 301void 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 311void 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 319void GeolocationPermissions::clearAll() 320{ 321 maybeLoadPermanentPermissions(); 322 s_permanentPermissions.clear(); 323 s_permanentPermissionsModified = true; 324} 325 326void 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 356void 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 393void 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 401bool 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 414void GeolocationPermissions::setAlwaysDeny(bool deny) 415{ 416 s_alwaysDeny = deny; 417} 418 419} // namespace android 420