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