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 "components/search_provider_logos/logo_cache.h" 6 7 #include "base/files/file_util.h" 8 #include "base/json/json_reader.h" 9 #include "base/json/json_writer.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/values.h" 12 13 namespace { 14 15 // The cached logo metadata is persisted as JSON using these keys. 16 const char kSourceUrlKey[] = "url"; 17 const char kExpirationTimeKey[] = "expiration_time"; 18 const char kCanShowAfterExpirationKey[] = "can_show_after_expiration"; 19 const char kFingerprintKey[] = "fingerprint"; 20 const char kOnClickURLKey[] = "on_click_url"; 21 const char kAltTextKey[] = "alt_text"; 22 const char kMimeTypeKey[] = "mime_type"; 23 const char kNumBytesKey[] = "num_bytes"; 24 25 bool GetTimeValue(const base::DictionaryValue& dict, 26 const std::string& key, 27 base::Time* time) { 28 std::string str; 29 int64 internal_time_value; 30 if (dict.GetString(key, &str) && 31 base::StringToInt64(str, &internal_time_value)) { 32 *time = base::Time::FromInternalValue(internal_time_value); 33 return true; 34 } 35 return false; 36 } 37 38 void SetTimeValue(base::DictionaryValue& dict, 39 const std::string& key, 40 const base::Time& time) { 41 int64 internal_time_value = time.ToInternalValue(); 42 dict.SetString(key, base::Int64ToString(internal_time_value)); 43 } 44 45 } // namespace 46 47 namespace search_provider_logos { 48 49 LogoCache::LogoCache(const base::FilePath& cache_directory) 50 : cache_directory_(cache_directory), 51 metadata_is_valid_(false) { 52 // The LogoCache can be constructed on any thread, as long as it's used 53 // on a single thread after construction. 54 thread_checker_.DetachFromThread(); 55 } 56 57 LogoCache::~LogoCache() { 58 DCHECK(thread_checker_.CalledOnValidThread()); 59 } 60 61 void LogoCache::UpdateCachedLogoMetadata(const LogoMetadata& metadata) { 62 DCHECK(thread_checker_.CalledOnValidThread()); 63 DCHECK(metadata_); 64 DCHECK_EQ(metadata_->fingerprint, metadata.fingerprint); 65 66 UpdateMetadata(make_scoped_ptr(new LogoMetadata(metadata))); 67 WriteMetadata(); 68 } 69 70 const LogoMetadata* LogoCache::GetCachedLogoMetadata() { 71 DCHECK(thread_checker_.CalledOnValidThread()); 72 ReadMetadataIfNeeded(); 73 return metadata_.get(); 74 } 75 76 void LogoCache::SetCachedLogo(const EncodedLogo* logo) { 77 DCHECK(thread_checker_.CalledOnValidThread()); 78 scoped_ptr<LogoMetadata> metadata; 79 if (logo) { 80 metadata.reset(new LogoMetadata(logo->metadata)); 81 logo_num_bytes_ = static_cast<int>(logo->encoded_image->size()); 82 } 83 UpdateMetadata(metadata.Pass()); 84 WriteLogo(logo ? logo->encoded_image : NULL); 85 } 86 87 scoped_ptr<EncodedLogo> LogoCache::GetCachedLogo() { 88 DCHECK(thread_checker_.CalledOnValidThread()); 89 90 ReadMetadataIfNeeded(); 91 if (!metadata_) 92 return scoped_ptr<EncodedLogo>(); 93 94 scoped_refptr<base::RefCountedString> encoded_image = 95 new base::RefCountedString(); 96 if (!base::ReadFileToString(GetLogoPath(), &encoded_image->data())) { 97 UpdateMetadata(scoped_ptr<LogoMetadata>()); 98 return scoped_ptr<EncodedLogo>(); 99 } 100 101 if (encoded_image->size() != static_cast<size_t>(logo_num_bytes_)) { 102 // Delete corrupt metadata and logo. 103 DeleteLogoAndMetadata(); 104 UpdateMetadata(scoped_ptr<LogoMetadata>()); 105 return scoped_ptr<EncodedLogo>(); 106 } 107 108 scoped_ptr<EncodedLogo> logo(new EncodedLogo()); 109 logo->encoded_image = encoded_image; 110 logo->metadata = *metadata_; 111 return logo.Pass(); 112 } 113 114 // static 115 scoped_ptr<LogoMetadata> LogoCache::LogoMetadataFromString( 116 const std::string& str, int* logo_num_bytes) { 117 scoped_ptr<base::Value> value(base::JSONReader::Read(str)); 118 base::DictionaryValue* dict; 119 if (!value || !value->GetAsDictionary(&dict)) 120 return scoped_ptr<LogoMetadata>(); 121 122 scoped_ptr<LogoMetadata> metadata(new LogoMetadata()); 123 if (!dict->GetString(kSourceUrlKey, &metadata->source_url) || 124 !dict->GetString(kFingerprintKey, &metadata->fingerprint) || 125 !dict->GetString(kOnClickURLKey, &metadata->on_click_url) || 126 !dict->GetString(kAltTextKey, &metadata->alt_text) || 127 !dict->GetString(kMimeTypeKey, &metadata->mime_type) || 128 !dict->GetBoolean(kCanShowAfterExpirationKey, 129 &metadata->can_show_after_expiration) || 130 !dict->GetInteger(kNumBytesKey, logo_num_bytes) || 131 !GetTimeValue(*dict, kExpirationTimeKey, &metadata->expiration_time)) { 132 return scoped_ptr<LogoMetadata>(); 133 } 134 135 return metadata.Pass(); 136 } 137 138 // static 139 void LogoCache::LogoMetadataToString(const LogoMetadata& metadata, 140 int num_bytes, 141 std::string* str) { 142 base::DictionaryValue dict; 143 dict.SetString(kSourceUrlKey, metadata.source_url); 144 dict.SetString(kFingerprintKey, metadata.fingerprint); 145 dict.SetString(kOnClickURLKey, metadata.on_click_url); 146 dict.SetString(kAltTextKey, metadata.alt_text); 147 dict.SetString(kMimeTypeKey, metadata.mime_type); 148 dict.SetBoolean(kCanShowAfterExpirationKey, 149 metadata.can_show_after_expiration); 150 dict.SetInteger(kNumBytesKey, num_bytes); 151 SetTimeValue(dict, kExpirationTimeKey, metadata.expiration_time); 152 base::JSONWriter::Write(&dict, str); 153 } 154 155 base::FilePath LogoCache::GetLogoPath() { 156 return cache_directory_.Append(FILE_PATH_LITERAL("logo")); 157 } 158 159 base::FilePath LogoCache::GetMetadataPath() { 160 return cache_directory_.Append(FILE_PATH_LITERAL("metadata")); 161 } 162 163 void LogoCache::UpdateMetadata(scoped_ptr<LogoMetadata> metadata) { 164 metadata_ = metadata.Pass(); 165 metadata_is_valid_ = true; 166 } 167 168 void LogoCache::ReadMetadataIfNeeded() { 169 if (metadata_is_valid_) 170 return; 171 172 scoped_ptr<LogoMetadata> metadata; 173 base::FilePath metadata_path = GetMetadataPath(); 174 std::string str; 175 if (base::ReadFileToString(metadata_path, &str)) { 176 metadata = LogoMetadataFromString(str, &logo_num_bytes_); 177 if (!metadata) { 178 // Delete corrupt metadata and logo. 179 DeleteLogoAndMetadata(); 180 } 181 } 182 183 UpdateMetadata(metadata.Pass()); 184 } 185 186 void LogoCache::WriteMetadata() { 187 if (!EnsureCacheDirectoryExists()) 188 return; 189 190 std::string str; 191 LogoMetadataToString(*metadata_, logo_num_bytes_, &str); 192 base::WriteFile(GetMetadataPath(), str.data(), static_cast<int>(str.size())); 193 } 194 195 void LogoCache::WriteLogo(scoped_refptr<base::RefCountedMemory> encoded_image) { 196 if (!EnsureCacheDirectoryExists()) 197 return; 198 199 if (!metadata_ || !encoded_image.get()) { 200 DeleteLogoAndMetadata(); 201 return; 202 } 203 204 // To minimize the chances of ending up in an undetectably broken state: 205 // First, delete the metadata file, then update the logo file, then update the 206 // metadata file. 207 base::FilePath logo_path = GetLogoPath(); 208 base::FilePath metadata_path = GetMetadataPath(); 209 210 if (!base::DeleteFile(metadata_path, false)) 211 return; 212 213 if (base::WriteFile( 214 logo_path, 215 encoded_image->front_as<char>(), 216 static_cast<int>(encoded_image->size())) == -1) { 217 base::DeleteFile(logo_path, false); 218 return; 219 } 220 221 WriteMetadata(); 222 } 223 224 void LogoCache::DeleteLogoAndMetadata() { 225 base::DeleteFile(GetLogoPath(), false); 226 base::DeleteFile(GetMetadataPath(), false); 227 } 228 229 bool LogoCache::EnsureCacheDirectoryExists() { 230 if (base::DirectoryExists(cache_directory_)) 231 return true; 232 return base::CreateDirectory(cache_directory_); 233 } 234 235 } // namespace search_provider_logos 236