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 "SQLiteStatement.h" 35 #include "SQLiteTransaction.h" 36 #include "WebViewCore.h" 37 38 using namespace WebCore; 39 40 namespace android { 41 42 GeolocationPermissions::PermissionsMap GeolocationPermissions::s_permanentPermissions; 43 GeolocationPermissions::GeolocationPermissionsVector GeolocationPermissions::s_instances; 44 bool GeolocationPermissions::s_alwaysDeny = false; 45 bool GeolocationPermissions::s_permanentPermissionsLoaded = false; 46 bool GeolocationPermissions::s_permanentPermissionsModified = false; 47 String GeolocationPermissions::s_databasePath; 48 49 static const char* databaseName = "/GeolocationPermissions.db"; 50 51 GeolocationPermissions::GeolocationPermissions(WebViewCore* webViewCore, Frame* mainFrame) 52 : m_webViewCore(webViewCore) 53 , m_mainFrame(mainFrame) 54 , m_timer(this, &GeolocationPermissions::timerFired) 55 56 { 57 ASSERT(m_webViewCore); 58 maybeLoadPermanentPermissions(); 59 s_instances.append(this); 60 } 61 62 GeolocationPermissions::~GeolocationPermissions() 63 { 64 size_t index = s_instances.find(this); 65 s_instances.remove(index); 66 } 67 68 void GeolocationPermissions::queryPermissionState(Frame* frame) 69 { 70 ASSERT(s_permanentPermissionsLoaded); 71 72 // We use SecurityOrigin::toString to key the map. Note that testing 73 // the SecurityOrigin pointer for equality is insufficient. 74 String originString = frame->document()->securityOrigin()->toString(); 75 76 // If we've been told to always deny requests, do so. 77 if (s_alwaysDeny) { 78 makeAsynchronousCallbackToGeolocation(originString, false); 79 return; 80 } 81 82 // See if we have a record for this origin in the permanent permissions. 83 // These take precedence over temporary permissions so that changes made 84 // from the browser settings work as intended. 85 PermissionsMap::const_iterator iter = s_permanentPermissions.find(originString); 86 PermissionsMap::const_iterator end = s_permanentPermissions.end(); 87 if (iter != end) { 88 bool allow = iter->second; 89 makeAsynchronousCallbackToGeolocation(originString, allow); 90 return; 91 } 92 93 // Check the temporary permisions. 94 iter = m_temporaryPermissions.find(originString); 95 end = m_temporaryPermissions.end(); 96 if (iter != end) { 97 bool allow = iter->second; 98 makeAsynchronousCallbackToGeolocation(originString, allow); 99 return; 100 } 101 102 // If there's no pending request, prompt the user. 103 if (nextOriginInQueue().isEmpty()) { 104 // Although multiple tabs may request permissions for the same origin 105 // simultaneously, the routing in WebViewCore/CallbackProxy ensures that 106 // the result of the request will make it back to this object, so 107 // there's no need for a globally unique ID for the request. 108 m_webViewCore->geolocationPermissionsShowPrompt(originString); 109 } 110 111 // Add this request to the queue so we can track which frames requested it. 112 if (m_queuedOrigins.find(originString) == WTF::notFound) { 113 m_queuedOrigins.append(originString); 114 FrameSet frameSet; 115 frameSet.add(frame); 116 m_queuedOriginsToFramesMap.add(originString, frameSet); 117 } else { 118 ASSERT(m_queuedOriginsToFramesMap.contains(originString)); 119 m_queuedOriginsToFramesMap.find(originString)->second.add(frame); 120 } 121 } 122 123 void GeolocationPermissions::cancelPermissionStateQuery(WebCore::Frame* frame) 124 { 125 // We cancel any queued request for the given frame. There can be at most 126 // one of these, since each frame maps to a single origin. We only cancel 127 // the request if this frame is the only one reqesting permission for this 128 // origin. 129 // 130 // We can use the origin string to avoid searching the map. 131 String originString = frame->document()->securityOrigin()->toString(); 132 size_t index = m_queuedOrigins.find(originString); 133 if (index == WTF::notFound) 134 return; 135 136 ASSERT(m_queuedOriginsToFramesMap.contains(originString)); 137 OriginToFramesMap::iterator iter = m_queuedOriginsToFramesMap.find(originString); 138 ASSERT(iter->second.contains(frame)); 139 iter->second.remove(frame); 140 if (!iter->second.isEmpty()) 141 return; 142 143 m_queuedOrigins.remove(index); 144 m_queuedOriginsToFramesMap.remove(iter); 145 146 // If this is the origin currently being shown, cancel the prompt 147 // and show the next in the queue, if present. 148 if (index == 0) { 149 m_webViewCore->geolocationPermissionsHidePrompt(); 150 if (!nextOriginInQueue().isEmpty()) 151 m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); 152 } 153 } 154 155 void GeolocationPermissions::makeAsynchronousCallbackToGeolocation(String origin, bool allow) 156 { 157 m_callbackData.origin = origin; 158 m_callbackData.allow = allow; 159 m_timer.startOneShot(0); 160 } 161 162 void GeolocationPermissions::providePermissionState(String origin, bool allow, bool remember) 163 { 164 ASSERT(s_permanentPermissionsLoaded); 165 166 // It's possible that this method is called with an origin that doesn't 167 // match m_originInProgress. This can occur if this object is reset 168 // while a permission result is in the process of being marshalled back to 169 // the WebCore thread from the browser. In this case, we simply ignore the 170 // call. 171 if (origin != nextOriginInQueue()) 172 return; 173 174 maybeCallbackFrames(origin, allow); 175 recordPermissionState(origin, allow, remember); 176 177 // If the permissions are set to be remembered, cancel any queued requests 178 // for this domain in other tabs. 179 if (remember) 180 cancelPendingRequestsInOtherTabs(origin); 181 182 // Clear the origin from the queue. 183 ASSERT(!m_queuedOrigins.isEmpty()); 184 m_queuedOrigins.remove(0); 185 ASSERT(m_queuedOriginsToFramesMap.contains(origin)); 186 m_queuedOriginsToFramesMap.remove(origin); 187 188 // If there are other requests queued, start the next one. 189 if (!nextOriginInQueue().isEmpty()) 190 m_webViewCore->geolocationPermissionsShowPrompt(nextOriginInQueue()); 191 } 192 193 void GeolocationPermissions::recordPermissionState(String origin, bool allow, bool remember) 194 { 195 if (remember) { 196 s_permanentPermissions.set(origin, allow); 197 s_permanentPermissionsModified = true; 198 } else { 199 // It's possible that another tab recorded a permanent permission for 200 // this origin while our request was in progress, but we record it 201 // anyway. 202 m_temporaryPermissions.set(origin, allow); 203 } 204 } 205 206 void GeolocationPermissions::cancelPendingRequestsInOtherTabs(String origin) 207 { 208 for (GeolocationPermissionsVector::const_iterator iter = s_instances.begin(); 209 iter != s_instances.end(); 210 ++iter) 211 (*iter)->cancelPendingRequests(origin); 212 } 213 214 void GeolocationPermissions::cancelPendingRequests(String origin) 215 { 216 size_t index = m_queuedOrigins.find(origin); 217 218 // Don't cancel the request if it's currently being shown, in which case 219 // it's at index 0. 220 if (index == WTF::notFound || !index) 221 return; 222 223 // Get the permission from the permanent list. 224 ASSERT(s_permanentPermissions.contains(origin)); 225 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); 226 bool allow = iter->second; 227 228 maybeCallbackFrames(origin, allow); 229 230 m_queuedOrigins.remove(index); 231 ASSERT(m_queuedOriginsToFramesMap.contains(origin)); 232 m_queuedOriginsToFramesMap.remove(origin); 233 } 234 235 void GeolocationPermissions::timerFired(Timer<GeolocationPermissions>* timer) 236 { 237 ASSERT_UNUSED(timer, timer == &m_timer); 238 maybeCallbackFrames(m_callbackData.origin, m_callbackData.allow); 239 } 240 241 void GeolocationPermissions::resetTemporaryPermissionStates() 242 { 243 ASSERT(s_permanentPermissionsLoaded); 244 m_queuedOrigins.clear(); 245 m_queuedOriginsToFramesMap.clear(); 246 m_temporaryPermissions.clear(); 247 // If any permission results are being marshalled back to this thread, this 248 // will render them inefective. 249 m_timer.stop(); 250 251 m_webViewCore->geolocationPermissionsHidePrompt(); 252 } 253 254 const WebCore::String& GeolocationPermissions::nextOriginInQueue() 255 { 256 static const String emptyString = ""; 257 return m_queuedOrigins.isEmpty() ? emptyString : m_queuedOrigins[0]; 258 } 259 260 void GeolocationPermissions::maybeCallbackFrames(String origin, bool allow) 261 { 262 // We can't track which frame issued the request, as frames can be deleted 263 // or have their contents replaced. Even uniqueChildName is not unique when 264 // frames are dynamically deleted and created. Instead, we simply call back 265 // to the Geolocation object in all frames from the correct origin. 266 for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) { 267 if (origin == frame->document()->securityOrigin()->toString()) { 268 // If the page has changed, it may no longer have a Geolocation 269 // object. 270 Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); 271 if (geolocation) 272 geolocation->setIsAllowed(allow); 273 } 274 } 275 } 276 277 GeolocationPermissions::OriginSet GeolocationPermissions::getOrigins() 278 { 279 maybeLoadPermanentPermissions(); 280 OriginSet origins; 281 PermissionsMap::const_iterator end = s_permanentPermissions.end(); 282 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) 283 origins.add(iter->first); 284 return origins; 285 } 286 287 bool GeolocationPermissions::getAllowed(String origin) 288 { 289 maybeLoadPermanentPermissions(); 290 bool allowed = false; 291 PermissionsMap::const_iterator iter = s_permanentPermissions.find(origin); 292 PermissionsMap::const_iterator end = s_permanentPermissions.end(); 293 if (iter != end) 294 allowed = iter->second; 295 return allowed; 296 } 297 298 void GeolocationPermissions::clear(String origin) 299 { 300 maybeLoadPermanentPermissions(); 301 PermissionsMap::iterator iter = s_permanentPermissions.find(origin); 302 if (iter != s_permanentPermissions.end()) { 303 s_permanentPermissions.remove(iter); 304 s_permanentPermissionsModified = true; 305 } 306 } 307 308 void GeolocationPermissions::allow(String origin) 309 { 310 maybeLoadPermanentPermissions(); 311 // We replace any existing permanent permission. 312 s_permanentPermissions.set(origin, true); 313 s_permanentPermissionsModified = true; 314 } 315 316 void GeolocationPermissions::clearAll() 317 { 318 maybeLoadPermanentPermissions(); 319 s_permanentPermissions.clear(); 320 s_permanentPermissionsModified = true; 321 } 322 323 void GeolocationPermissions::maybeLoadPermanentPermissions() 324 { 325 if (s_permanentPermissionsLoaded) 326 return; 327 s_permanentPermissionsLoaded = true; 328 329 SQLiteDatabase database; 330 if (!database.open(s_databasePath + databaseName)) 331 return; 332 333 // Create the table here, such that even if we've just created the DB, the 334 // commands below should succeed. 335 if (!database.executeCommand("CREATE TABLE IF NOT EXISTS Permissions (origin TEXT UNIQUE NOT NULL, allow INTEGER NOT NULL)")) { 336 database.close(); 337 return; 338 } 339 340 SQLiteStatement statement(database, "SELECT * FROM Permissions"); 341 if (statement.prepare() != SQLResultOk) { 342 database.close(); 343 return; 344 } 345 346 ASSERT(s_permanentPermissions.size() == 0); 347 while (statement.step() == SQLResultRow) 348 s_permanentPermissions.set(statement.getColumnText(0), statement.getColumnInt64(1)); 349 350 database.close(); 351 } 352 353 void GeolocationPermissions::maybeStorePermanentPermissions() 354 { 355 // If the permanent permissions haven't been modified, there's no need to 356 // save them to the DB. (If we haven't even loaded them, writing them now 357 // would overwrite the stored permissions with the empty set.) 358 if (!s_permanentPermissionsModified) 359 return; 360 361 SQLiteDatabase database; 362 if (!database.open(s_databasePath + databaseName)) 363 return; 364 365 SQLiteTransaction transaction(database); 366 367 // The number of entries should be small enough that it's not worth trying 368 // to perform a diff. Simply clear the table and repopulate it. 369 if (!database.executeCommand("DELETE FROM Permissions")) { 370 database.close(); 371 return; 372 } 373 374 PermissionsMap::const_iterator end = s_permanentPermissions.end(); 375 for (PermissionsMap::const_iterator iter = s_permanentPermissions.begin(); iter != end; ++iter) { 376 SQLiteStatement statement(database, "INSERT INTO Permissions (origin, allow) VALUES (?, ?)"); 377 if (statement.prepare() != SQLResultOk) 378 continue; 379 statement.bindText(1, iter->first); 380 statement.bindInt64(2, iter->second); 381 statement.executeCommand(); 382 } 383 384 transaction.commit(); 385 database.close(); 386 387 s_permanentPermissionsModified = false; 388 } 389 390 void GeolocationPermissions::setDatabasePath(String path) 391 { 392 // Take the first non-empty value. 393 if (s_databasePath.length() > 0) 394 return; 395 s_databasePath = path; 396 } 397 398 void GeolocationPermissions::setAlwaysDeny(bool deny) 399 { 400 s_alwaysDeny = deny; 401 } 402 403 } // namespace android 404