1 // Copyright 2014 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 "extensions/browser/api/storage/settings_storage_quota_enforcer.h" 6 7 #include "base/bind.h" 8 #include "base/json/json_writer.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/metrics/histogram.h" 12 #include "base/stl_util.h" 13 #include "base/strings/stringprintf.h" 14 #include "extensions/browser/value_store/value_store_util.h" 15 #include "extensions/common/extension_api.h" 16 17 namespace util = value_store_util; 18 19 namespace extensions { 20 21 namespace { 22 23 // Resources there are a quota for. 24 enum Resource { 25 QUOTA_BYTES, 26 QUOTA_BYTES_PER_ITEM, 27 MAX_ITEMS 28 }; 29 30 // Allocates a setting in a record of total and per-setting usage. 31 void Allocate( 32 const std::string& key, 33 const base::Value& value, 34 size_t* used_total, 35 std::map<std::string, size_t>* used_per_setting) { 36 // Calculate the setting size based on its JSON serialization size. 37 // TODO(kalman): Does this work with different encodings? 38 // TODO(kalman): This is duplicating work that the leveldb delegate 39 // implementation is about to do, and it would be nice to avoid this. 40 std::string value_as_json; 41 base::JSONWriter::Write(&value, &value_as_json); 42 size_t new_size = key.size() + value_as_json.size(); 43 size_t existing_size = (*used_per_setting)[key]; 44 45 *used_total += (new_size - existing_size); 46 (*used_per_setting)[key] = new_size; 47 } 48 49 // Frees the allocation of a setting in a record of total and per-setting usage. 50 void Free( 51 size_t* used_total, 52 std::map<std::string, size_t>* used_per_setting, 53 const std::string& key) { 54 *used_total -= (*used_per_setting)[key]; 55 used_per_setting->erase(key); 56 } 57 58 scoped_ptr<ValueStore::Error> QuotaExceededError(Resource resource, 59 scoped_ptr<std::string> key) { 60 const char* name = NULL; 61 // TODO(kalman): These hisograms are both silly and untracked. Fix. 62 switch (resource) { 63 case QUOTA_BYTES: 64 name = "QUOTA_BYTES"; 65 UMA_HISTOGRAM_COUNTS_100( 66 "Extensions.SettingsQuotaExceeded.TotalBytes", 1); 67 break; 68 case QUOTA_BYTES_PER_ITEM: 69 name = "QUOTA_BYTES_PER_ITEM"; 70 UMA_HISTOGRAM_COUNTS_100( 71 "Extensions.SettingsQuotaExceeded.BytesPerSetting", 1); 72 break; 73 case MAX_ITEMS: 74 name = "MAX_ITEMS"; 75 UMA_HISTOGRAM_COUNTS_100( 76 "Extensions.SettingsQuotaExceeded.KeyCount", 1); 77 break; 78 } 79 CHECK(name); 80 return make_scoped_ptr(new ValueStore::Error( 81 ValueStore::QUOTA_EXCEEDED, 82 base::StringPrintf("%s quota exceeded", name), 83 key.Pass())); 84 } 85 86 } // namespace 87 88 SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer( 89 const Limits& limits, ValueStore* delegate) 90 : limits_(limits), delegate_(delegate), used_total_(0) { 91 CalculateUsage(); 92 } 93 94 SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {} 95 96 size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string& key) { 97 std::map<std::string, size_t>::iterator maybe_used = 98 used_per_setting_.find(key); 99 return maybe_used == used_per_setting_.end() ? 0u : maybe_used->second; 100 } 101 102 size_t SettingsStorageQuotaEnforcer::GetBytesInUse( 103 const std::vector<std::string>& keys) { 104 size_t used = 0; 105 for (std::vector<std::string>::const_iterator it = keys.begin(); 106 it != keys.end(); ++it) { 107 used += GetBytesInUse(*it); 108 } 109 return used; 110 } 111 112 size_t SettingsStorageQuotaEnforcer::GetBytesInUse() { 113 // All ValueStore implementations rely on GetBytesInUse being 114 // implemented here. 115 return used_total_; 116 } 117 118 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get( 119 const std::string& key) { 120 return delegate_->Get(key); 121 } 122 123 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get( 124 const std::vector<std::string>& keys) { 125 return delegate_->Get(keys); 126 } 127 128 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() { 129 return delegate_->Get(); 130 } 131 132 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set( 133 WriteOptions options, const std::string& key, const base::Value& value) { 134 size_t new_used_total = used_total_; 135 std::map<std::string, size_t> new_used_per_setting = used_per_setting_; 136 Allocate(key, value, &new_used_total, &new_used_per_setting); 137 138 if (!(options & IGNORE_QUOTA)) { 139 if (new_used_total > limits_.quota_bytes) { 140 return MakeWriteResult( 141 QuotaExceededError(QUOTA_BYTES, util::NewKey(key))); 142 } 143 if (new_used_per_setting[key] > limits_.quota_bytes_per_item) { 144 return MakeWriteResult( 145 QuotaExceededError(QUOTA_BYTES_PER_ITEM, util::NewKey(key))); 146 } 147 if (new_used_per_setting.size() > limits_.max_items) 148 return MakeWriteResult(QuotaExceededError(MAX_ITEMS, util::NewKey(key))); 149 } 150 151 WriteResult result = delegate_->Set(options, key, value); 152 if (result->HasError()) { 153 return result.Pass(); 154 } 155 156 used_total_ = new_used_total; 157 used_per_setting_.swap(new_used_per_setting); 158 return result.Pass(); 159 } 160 161 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set( 162 WriteOptions options, const base::DictionaryValue& values) { 163 size_t new_used_total = used_total_; 164 std::map<std::string, size_t> new_used_per_setting = used_per_setting_; 165 for (base::DictionaryValue::Iterator it(values); !it.IsAtEnd(); 166 it.Advance()) { 167 Allocate(it.key(), it.value(), &new_used_total, &new_used_per_setting); 168 169 if (!(options & IGNORE_QUOTA) && 170 new_used_per_setting[it.key()] > limits_.quota_bytes_per_item) { 171 return MakeWriteResult( 172 QuotaExceededError(QUOTA_BYTES_PER_ITEM, util::NewKey(it.key()))); 173 } 174 } 175 176 if (!(options & IGNORE_QUOTA)) { 177 if (new_used_total > limits_.quota_bytes) 178 return MakeWriteResult(QuotaExceededError(QUOTA_BYTES, util::NoKey())); 179 if (new_used_per_setting.size() > limits_.max_items) 180 return MakeWriteResult(QuotaExceededError(MAX_ITEMS, util::NoKey())); 181 } 182 183 WriteResult result = delegate_->Set(options, values); 184 if (result->HasError()) { 185 return result.Pass(); 186 } 187 188 used_total_ = new_used_total; 189 used_per_setting_ = new_used_per_setting; 190 return result.Pass(); 191 } 192 193 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove( 194 const std::string& key) { 195 WriteResult result = delegate_->Remove(key); 196 if (result->HasError()) { 197 return result.Pass(); 198 } 199 Free(&used_total_, &used_per_setting_, key); 200 return result.Pass(); 201 } 202 203 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove( 204 const std::vector<std::string>& keys) { 205 WriteResult result = delegate_->Remove(keys); 206 if (result->HasError()) { 207 return result.Pass(); 208 } 209 210 for (std::vector<std::string>::const_iterator it = keys.begin(); 211 it != keys.end(); ++it) { 212 Free(&used_total_, &used_per_setting_, *it); 213 } 214 return result.Pass(); 215 } 216 217 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() { 218 WriteResult result = delegate_->Clear(); 219 if (result->HasError()) { 220 return result.Pass(); 221 } 222 223 used_per_setting_.clear(); 224 used_total_ = 0; 225 return result.Pass(); 226 } 227 228 bool SettingsStorageQuotaEnforcer::Restore() { 229 if (!delegate_->Restore()) { 230 // If we failed, we can't calculate the usage - that's okay, though, because 231 // next time we Restore() (if it succeeds) we will recalculate usage anyway. 232 // So reset storage counters now to free up resources. 233 used_per_setting_.clear(); 234 used_total_ = 0u; 235 return false; 236 } 237 CalculateUsage(); 238 return true; 239 } 240 241 bool SettingsStorageQuotaEnforcer::RestoreKey(const std::string& key) { 242 if (!delegate_->RestoreKey(key)) 243 return false; 244 245 ReadResult result = Get(key); 246 // If the key was deleted as a result of the Restore() call, free it. 247 if (!result->settings().HasKey(key) && ContainsKey(used_per_setting_, key)) 248 Free(&used_total_, &used_per_setting_, key); 249 return true; 250 } 251 252 void SettingsStorageQuotaEnforcer::CalculateUsage() { 253 ReadResult maybe_settings = delegate_->Get(); 254 if (maybe_settings->HasError()) { 255 // Try to restore the database if it's corrupt. 256 if (maybe_settings->error().code == ValueStore::CORRUPTION && 257 delegate_->Restore()) { 258 maybe_settings = delegate_->Get(); 259 } else { 260 LOG(WARNING) << "Failed to get settings for quota:" 261 << maybe_settings->error().message; 262 return; 263 } 264 } 265 266 for (base::DictionaryValue::Iterator it(maybe_settings->settings()); 267 !it.IsAtEnd(); 268 it.Advance()) { 269 Allocate(it.key(), it.value(), &used_total_, &used_per_setting_); 270 } 271 } 272 273 } // namespace extensions 274