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 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) 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 64GeolocationPermissions::~GeolocationPermissions() 65{ 66 size_t index = s_instances.find(this); 67 s_instances.remove(index); 68} 69 70void 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 125void 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 157void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow) 158{ 159 m_callbackData.origin = origin; 160 m_callbackData.allow = allow; 161 m_timer.startOneShot(0); 162} 163 164void 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 195void 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 208void 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 216void 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 237void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer) 238{ 239 ASSERT_UNUSED(timer, timer == &m_timer); 240 maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow); 241} 242 243void 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 256const WTF::String& GeolocationPermissions::nextOriginInQueue() 257{ 258 static const String emptyString = ""; 259 return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0]; 260} 261 262void 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 279GeolocationPermissions::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 289bool 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 300void 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 310void 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 318void GeolocationPermissions::clearAll() 319{ 320 maybeLoadPermanentPermissions(); 321 s_permanentPermissions.clear(); 322 s_permanentPermissionsModified = true; 323} 324 325void 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 355void 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 392void 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 400bool 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 413void GeolocationPermissions::setAlwaysDeny(bool deny) 414{ 415 s_alwaysDeny = deny; 416} 417 418} // namespace android 419