1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "apps/saved_files_service.h" 6 7 #include <algorithm> 8 9 #include "apps/saved_files_service_factory.h" 10 #include "base/basictypes.h" 11 #include "base/containers/hash_tables.h" 12 #include "base/value_conversions.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "content/public/browser/notification_service.h" 16 #include "extensions/browser/extension_host.h" 17 #include "extensions/browser/extension_prefs.h" 18 #include "extensions/browser/extension_system.h" 19 #include "extensions/browser/extension_util.h" 20 #include "extensions/browser/notification_types.h" 21 #include "extensions/common/permissions/api_permission.h" 22 #include "extensions/common/permissions/permission_set.h" 23 #include "extensions/common/permissions/permissions_data.h" 24 25 namespace apps { 26 27 using extensions::APIPermission; 28 using extensions::Extension; 29 using extensions::ExtensionHost; 30 using extensions::ExtensionPrefs; 31 32 namespace { 33 34 // Preference keys 35 36 // The file entries that the app has permission to access. 37 const char kFileEntries[] = "file_entries"; 38 39 // The path to a file entry that the app had permission to access. 40 const char kFileEntryPath[] = "path"; 41 42 // Whether or not the the entry refers to a directory. 43 const char kFileEntryIsDirectory[] = "is_directory"; 44 45 // The sequence number in the LRU of the file entry. 46 const char kFileEntrySequenceNumber[] = "sequence_number"; 47 48 const size_t kMaxSavedFileEntries = 500; 49 const int kMaxSequenceNumber = kint32max; 50 51 // These might be different to the constant values in tests. 52 size_t g_max_saved_file_entries = kMaxSavedFileEntries; 53 int g_max_sequence_number = kMaxSequenceNumber; 54 55 // Persists a SavedFileEntry in ExtensionPrefs. 56 void AddSavedFileEntry(ExtensionPrefs* prefs, 57 const std::string& extension_id, 58 const SavedFileEntry& file_entry) { 59 ExtensionPrefs::ScopedDictionaryUpdate update( 60 prefs, extension_id, kFileEntries); 61 base::DictionaryValue* file_entries = update.Get(); 62 if (!file_entries) 63 file_entries = update.Create(); 64 DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL)); 65 66 base::DictionaryValue* file_entry_dict = new base::DictionaryValue(); 67 file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path)); 68 file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory); 69 file_entry_dict->SetInteger(kFileEntrySequenceNumber, 70 file_entry.sequence_number); 71 file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict); 72 } 73 74 // Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs. 75 void UpdateSavedFileEntry(ExtensionPrefs* prefs, 76 const std::string& extension_id, 77 const SavedFileEntry& file_entry) { 78 ExtensionPrefs::ScopedDictionaryUpdate update( 79 prefs, extension_id, kFileEntries); 80 base::DictionaryValue* file_entries = update.Get(); 81 DCHECK(file_entries); 82 base::DictionaryValue* file_entry_dict = NULL; 83 file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, 84 &file_entry_dict); 85 DCHECK(file_entry_dict); 86 file_entry_dict->SetInteger(kFileEntrySequenceNumber, 87 file_entry.sequence_number); 88 } 89 90 // Removes a SavedFileEntry from ExtensionPrefs. 91 void RemoveSavedFileEntry(ExtensionPrefs* prefs, 92 const std::string& extension_id, 93 const std::string& file_entry_id) { 94 ExtensionPrefs::ScopedDictionaryUpdate update( 95 prefs, extension_id, kFileEntries); 96 base::DictionaryValue* file_entries = update.Get(); 97 if (!file_entries) 98 file_entries = update.Create(); 99 file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL); 100 } 101 102 // Clears all SavedFileEntry for the app from ExtensionPrefs. 103 void ClearSavedFileEntries(ExtensionPrefs* prefs, 104 const std::string& extension_id) { 105 prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL); 106 } 107 108 // Returns all SavedFileEntries for the app. 109 std::vector<SavedFileEntry> GetSavedFileEntries( 110 ExtensionPrefs* prefs, 111 const std::string& extension_id) { 112 std::vector<SavedFileEntry> result; 113 const base::DictionaryValue* file_entries = NULL; 114 if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries)) 115 return result; 116 117 for (base::DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd(); 118 it.Advance()) { 119 const base::DictionaryValue* file_entry = NULL; 120 if (!it.value().GetAsDictionary(&file_entry)) 121 continue; 122 const base::Value* path_value; 123 if (!file_entry->Get(kFileEntryPath, &path_value)) 124 continue; 125 base::FilePath file_path; 126 if (!GetValueAsFilePath(*path_value, &file_path)) 127 continue; 128 bool is_directory = false; 129 file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory); 130 int sequence_number = 0; 131 if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number)) 132 continue; 133 if (!sequence_number) 134 continue; 135 result.push_back( 136 SavedFileEntry(it.key(), file_path, is_directory, sequence_number)); 137 } 138 return result; 139 } 140 141 } // namespace 142 143 SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {} 144 145 SavedFileEntry::SavedFileEntry(const std::string& id, 146 const base::FilePath& path, 147 bool is_directory, 148 int sequence_number) 149 : id(id), 150 path(path), 151 is_directory(is_directory), 152 sequence_number(sequence_number) {} 153 154 class SavedFilesService::SavedFiles { 155 public: 156 SavedFiles(Profile* profile, const std::string& extension_id); 157 ~SavedFiles(); 158 159 void RegisterFileEntry(const std::string& id, 160 const base::FilePath& file_path, 161 bool is_directory); 162 void EnqueueFileEntry(const std::string& id); 163 bool IsRegistered(const std::string& id) const; 164 const SavedFileEntry* GetFileEntry(const std::string& id) const; 165 std::vector<SavedFileEntry> GetAllFileEntries() const; 166 167 private: 168 // Compacts sequence numbers if the largest sequence number is 169 // g_max_sequence_number. Outside of testing, it is set to kint32max, so this 170 // will almost never do any real work. 171 void MaybeCompactSequenceNumbers(); 172 173 void LoadSavedFileEntriesFromPreferences(); 174 175 Profile* profile_; 176 const std::string extension_id_; 177 178 // Contains all file entries that have been registered, keyed by ID. Owns 179 // values. 180 base::hash_map<std::string, SavedFileEntry*> registered_file_entries_; 181 STLValueDeleter<base::hash_map<std::string, SavedFileEntry*> > 182 registered_file_entries_deleter_; 183 184 // The queue of file entries that have been retained, keyed by 185 // sequence_number. Values are a subset of values in registered_file_entries_. 186 // This should be kept in sync with file entries stored in extension prefs. 187 std::map<int, SavedFileEntry*> saved_file_lru_; 188 189 DISALLOW_COPY_AND_ASSIGN(SavedFiles); 190 }; 191 192 // static 193 SavedFilesService* SavedFilesService::Get(Profile* profile) { 194 return SavedFilesServiceFactory::GetForProfile(profile); 195 } 196 197 SavedFilesService::SavedFilesService(Profile* profile) 198 : extension_id_to_saved_files_deleter_(&extension_id_to_saved_files_), 199 profile_(profile) { 200 registrar_.Add(this, 201 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, 202 content::NotificationService::AllSources()); 203 registrar_.Add(this, 204 chrome::NOTIFICATION_APP_TERMINATING, 205 content::NotificationService::AllSources()); 206 } 207 208 SavedFilesService::~SavedFilesService() {} 209 210 void SavedFilesService::Observe(int type, 211 const content::NotificationSource& source, 212 const content::NotificationDetails& details) { 213 switch (type) { 214 case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: { 215 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 216 const Extension* extension = host->extension(); 217 if (extension) { 218 ClearQueueIfNoRetainPermission(extension); 219 Clear(extension->id()); 220 } 221 break; 222 } 223 224 case chrome::NOTIFICATION_APP_TERMINATING: { 225 // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular 226 // as all extension hosts will be destroyed as a result of shutdown. 227 registrar_.RemoveAll(); 228 break; 229 } 230 } 231 } 232 233 void SavedFilesService::RegisterFileEntry(const std::string& extension_id, 234 const std::string& id, 235 const base::FilePath& file_path, 236 bool is_directory) { 237 GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory); 238 } 239 240 void SavedFilesService::EnqueueFileEntry(const std::string& extension_id, 241 const std::string& id) { 242 GetOrInsert(extension_id)->EnqueueFileEntry(id); 243 } 244 245 std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries( 246 const std::string& extension_id) { 247 SavedFiles* saved_files = Get(extension_id); 248 if (saved_files) 249 return saved_files->GetAllFileEntries(); 250 return GetSavedFileEntries(ExtensionPrefs::Get(profile_), extension_id); 251 } 252 253 bool SavedFilesService::IsRegistered(const std::string& extension_id, 254 const std::string& id) { 255 return GetOrInsert(extension_id)->IsRegistered(id); 256 } 257 258 const SavedFileEntry* SavedFilesService::GetFileEntry( 259 const std::string& extension_id, 260 const std::string& id) { 261 return GetOrInsert(extension_id)->GetFileEntry(id); 262 } 263 264 void SavedFilesService::ClearQueueIfNoRetainPermission( 265 const Extension* extension) { 266 if (extensions::util::IsEphemeralApp(extension->id(), profile_) || 267 !extension->permissions_data()->active_permissions()->HasAPIPermission( 268 APIPermission::kFileSystemRetainEntries)) { 269 ClearQueue(extension); 270 } 271 } 272 273 void SavedFilesService::ClearQueue(const extensions::Extension* extension) { 274 ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id()); 275 Clear(extension->id()); 276 } 277 278 SavedFilesService::SavedFiles* SavedFilesService::Get( 279 const std::string& extension_id) const { 280 std::map<std::string, SavedFiles*>::const_iterator it = 281 extension_id_to_saved_files_.find(extension_id); 282 if (it != extension_id_to_saved_files_.end()) 283 return it->second; 284 285 return NULL; 286 } 287 288 SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert( 289 const std::string& extension_id) { 290 SavedFiles* saved_files = Get(extension_id); 291 if (saved_files) 292 return saved_files; 293 294 saved_files = new SavedFiles(profile_, extension_id); 295 extension_id_to_saved_files_.insert( 296 std::make_pair(extension_id, saved_files)); 297 return saved_files; 298 } 299 300 void SavedFilesService::Clear(const std::string& extension_id) { 301 std::map<std::string, SavedFiles*>::iterator it = 302 extension_id_to_saved_files_.find(extension_id); 303 if (it != extension_id_to_saved_files_.end()) { 304 delete it->second; 305 extension_id_to_saved_files_.erase(it); 306 } 307 } 308 309 SavedFilesService::SavedFiles::SavedFiles(Profile* profile, 310 const std::string& extension_id) 311 : profile_(profile), 312 extension_id_(extension_id), 313 registered_file_entries_deleter_(®istered_file_entries_) { 314 LoadSavedFileEntriesFromPreferences(); 315 } 316 317 SavedFilesService::SavedFiles::~SavedFiles() {} 318 319 void SavedFilesService::SavedFiles::RegisterFileEntry( 320 const std::string& id, 321 const base::FilePath& file_path, 322 bool is_directory) { 323 if (ContainsKey(registered_file_entries_, id)) 324 return; 325 326 registered_file_entries_.insert( 327 std::make_pair(id, new SavedFileEntry(id, file_path, is_directory, 0))); 328 } 329 330 void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) { 331 base::hash_map<std::string, SavedFileEntry*>::iterator it = 332 registered_file_entries_.find(id); 333 DCHECK(it != registered_file_entries_.end()); 334 335 SavedFileEntry* file_entry = it->second; 336 int old_sequence_number = file_entry->sequence_number; 337 if (!saved_file_lru_.empty()) { 338 // Get the sequence number after the last file entry in the LRU. 339 std::map<int, SavedFileEntry*>::reverse_iterator it = 340 saved_file_lru_.rbegin(); 341 if (it->second == file_entry) 342 return; 343 344 file_entry->sequence_number = it->first + 1; 345 } else { 346 // The first sequence number is 1, as 0 means the entry is not in the LRU. 347 file_entry->sequence_number = 1; 348 } 349 saved_file_lru_.insert( 350 std::make_pair(file_entry->sequence_number, file_entry)); 351 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 352 if (old_sequence_number) { 353 saved_file_lru_.erase(old_sequence_number); 354 UpdateSavedFileEntry(prefs, extension_id_, *file_entry); 355 } else { 356 AddSavedFileEntry(prefs, extension_id_, *file_entry); 357 if (saved_file_lru_.size() > g_max_saved_file_entries) { 358 std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); 359 it->second->sequence_number = 0; 360 RemoveSavedFileEntry(prefs, extension_id_, it->second->id); 361 saved_file_lru_.erase(it); 362 } 363 } 364 MaybeCompactSequenceNumbers(); 365 } 366 367 bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const { 368 return ContainsKey(registered_file_entries_, id); 369 } 370 371 const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry( 372 const std::string& id) const { 373 base::hash_map<std::string, SavedFileEntry*>::const_iterator it = 374 registered_file_entries_.find(id); 375 if (it == registered_file_entries_.end()) 376 return NULL; 377 378 return it->second; 379 } 380 381 std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries() 382 const { 383 std::vector<SavedFileEntry> result; 384 for (base::hash_map<std::string, SavedFileEntry*>::const_iterator it = 385 registered_file_entries_.begin(); 386 it != registered_file_entries_.end(); 387 ++it) { 388 result.push_back(*it->second); 389 } 390 return result; 391 } 392 393 void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() { 394 DCHECK_GE(g_max_sequence_number, 0); 395 DCHECK_GE(static_cast<size_t>(g_max_sequence_number), 396 g_max_saved_file_entries); 397 std::map<int, SavedFileEntry*>::reverse_iterator it = 398 saved_file_lru_.rbegin(); 399 if (it == saved_file_lru_.rend()) 400 return; 401 402 // Only compact sequence numbers if the last entry's sequence number is the 403 // maximum value. This should almost never be the case. 404 if (it->first < g_max_sequence_number) 405 return; 406 407 int sequence_number = 0; 408 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 409 for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); 410 it != saved_file_lru_.end(); 411 ++it) { 412 sequence_number++; 413 if (it->second->sequence_number == sequence_number) 414 continue; 415 416 SavedFileEntry* file_entry = it->second; 417 file_entry->sequence_number = sequence_number; 418 UpdateSavedFileEntry(prefs, extension_id_, *file_entry); 419 saved_file_lru_.erase(it++); 420 // Provide the following element as an insert hint. While optimized 421 // insertion time with the following element as a hint is only supported by 422 // the spec in C++11, the implementations do support this. 423 it = saved_file_lru_.insert( 424 it, std::make_pair(file_entry->sequence_number, file_entry)); 425 } 426 } 427 428 void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() { 429 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 430 std::vector<SavedFileEntry> saved_entries = 431 GetSavedFileEntries(prefs, extension_id_); 432 for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin(); 433 it != saved_entries.end(); 434 ++it) { 435 SavedFileEntry* file_entry = new SavedFileEntry(*it); 436 registered_file_entries_.insert(std::make_pair(file_entry->id, file_entry)); 437 saved_file_lru_.insert( 438 std::make_pair(file_entry->sequence_number, file_entry)); 439 } 440 } 441 442 // static 443 void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) { 444 g_max_sequence_number = max_value; 445 } 446 447 // static 448 void SavedFilesService::ClearMaxSequenceNumberForTest() { 449 g_max_sequence_number = kMaxSequenceNumber; 450 } 451 452 // static 453 void SavedFilesService::SetLruSizeForTest(int size) { 454 g_max_saved_file_entries = size; 455 } 456 457 // static 458 void SavedFilesService::ClearLruSizeForTest() { 459 g_max_saved_file_entries = kMaxSavedFileEntries; 460 } 461 462 } // namespace apps 463