1 // Copyright (c) 2012 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 "chrome/browser/search_engines/util.h" 6 7 #include <map> 8 #include <set> 9 #include <string> 10 #include <vector> 11 12 #include "base/logging.h" 13 #include "base/memory/scoped_vector.h" 14 #include "base/prefs/pref_service.h" 15 #include "base/time/time.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/search_engines/template_url.h" 18 #include "chrome/browser/search_engines/template_url_prepopulate_data.h" 19 #include "chrome/browser/search_engines/template_url_service.h" 20 #include "chrome/browser/search_engines/template_url_service_factory.h" 21 #include "content/public/browser/browser_thread.h" 22 23 using content::BrowserThread; 24 25 base::string16 GetDefaultSearchEngineName(Profile* profile) { 26 if (!profile) { 27 NOTREACHED(); 28 return base::string16(); 29 } 30 const TemplateURL* const default_provider = 31 TemplateURLServiceFactory::GetForProfile(profile)-> 32 GetDefaultSearchProvider(); 33 if (!default_provider) { 34 // TODO(cpu): bug 1187517. It is possible to have no default provider. 35 // returning an empty string is a stopgap measure for the crash 36 // http://code.google.com/p/chromium/issues/detail?id=2573 37 return base::string16(); 38 } 39 return default_provider->short_name(); 40 } 41 42 GURL GetDefaultSearchURLForSearchTerms(Profile* profile, 43 const base::string16& terms) { 44 DCHECK(profile); 45 TemplateURLService* service = 46 TemplateURLServiceFactory::GetForProfile(profile); 47 const TemplateURL* default_provider = service->GetDefaultSearchProvider(); 48 if (!default_provider) 49 return GURL(); 50 const TemplateURLRef& search_url = default_provider->url_ref(); 51 DCHECK(search_url.SupportsReplacement(service->search_terms_data())); 52 TemplateURLRef::SearchTermsArgs search_terms_args(terms); 53 search_terms_args.append_extra_query_params = true; 54 return GURL(search_url.ReplaceSearchTerms(search_terms_args, 55 service->search_terms_data())); 56 } 57 58 void RemoveDuplicatePrepopulateIDs( 59 WebDataService* service, 60 const ScopedVector<TemplateURLData>& prepopulated_urls, 61 TemplateURL* default_search_provider, 62 TemplateURLService::TemplateURLVector* template_urls, 63 const SearchTermsData& search_terms_data, 64 std::set<std::string>* removed_keyword_guids) { 65 DCHECK(service == NULL || BrowserThread::CurrentlyOn(BrowserThread::UI)); 66 DCHECK(template_urls); 67 68 // For convenience construct an ID->TemplateURL* map from |prepopulated_urls|. 69 typedef std::map<int, TemplateURLData*> PrepopulatedURLMap; 70 PrepopulatedURLMap prepopulated_url_map; 71 for (std::vector<TemplateURLData*>::const_iterator i( 72 prepopulated_urls.begin()); 73 i != prepopulated_urls.end(); 74 ++i) 75 prepopulated_url_map[(*i)->prepopulate_id] = *i; 76 77 // Separate |template_urls| into prepopulated and non-prepopulated groups. 78 typedef std::multimap<int, TemplateURL*> UncheckedURLMap; 79 UncheckedURLMap unchecked_urls; 80 TemplateURLService::TemplateURLVector checked_urls; 81 for (TemplateURLService::TemplateURLVector::iterator i( 82 template_urls->begin()); i != template_urls->end(); ++i) { 83 TemplateURL* turl = *i; 84 int prepopulate_id = turl->prepopulate_id(); 85 if (prepopulate_id) 86 unchecked_urls.insert(std::make_pair(prepopulate_id, turl)); 87 else 88 checked_urls.push_back(turl); 89 } 90 91 // For each group of prepopulated URLs with one ID, find the best URL to use 92 // and add it to the (initially all non-prepopulated) URLs we've already OKed. 93 // Delete the others from the service and from memory. 94 while (!unchecked_urls.empty()) { 95 // Find the best URL. 96 int prepopulate_id = unchecked_urls.begin()->first; 97 PrepopulatedURLMap::const_iterator prepopulated_url = 98 prepopulated_url_map.find(prepopulate_id); 99 UncheckedURLMap::iterator end = unchecked_urls.upper_bound(prepopulate_id); 100 UncheckedURLMap::iterator best = unchecked_urls.begin(); 101 bool matched_keyword = false; 102 for (UncheckedURLMap::iterator i = unchecked_urls.begin(); i != end; ++i) { 103 // If the user-selected DSE is a prepopulated engine its properties will 104 // either come from the prepopulation origin or from the user preferences 105 // file (see DefaultSearchManager). Those properties will end up 106 // overwriting whatever we load now anyway. If we are eliminating 107 // duplicates, then, we err on the side of keeping the thing that looks 108 // more like the value we will end up with in the end. 109 if (default_search_provider && 110 (default_search_provider->prepopulate_id() == 111 i->second->prepopulate_id()) && 112 default_search_provider->HasSameKeywordAs(i->second->data(), 113 search_terms_data)) { 114 best = i; 115 break; 116 } 117 118 // Otherwise, a URL is best if it matches the prepopulated data's keyword; 119 // if none match, just fall back to using the one with the lowest ID. 120 if (matched_keyword) 121 continue; 122 if ((prepopulated_url != prepopulated_url_map.end()) && 123 i->second->HasSameKeywordAs(*prepopulated_url->second, 124 search_terms_data)) { 125 best = i; 126 matched_keyword = true; 127 } else if (i->second->id() < best->second->id()) { 128 best = i; 129 } 130 } 131 132 // Add the best URL to the checked group and delete the rest. 133 checked_urls.push_back(best->second); 134 for (UncheckedURLMap::iterator i = unchecked_urls.begin(); i != end; ++i) { 135 if (i == best) 136 continue; 137 if (service) { 138 service->RemoveKeyword(i->second->id()); 139 if (removed_keyword_guids) 140 removed_keyword_guids->insert(i->second->sync_guid()); 141 } 142 delete i->second; 143 } 144 145 // Done with this group. 146 unchecked_urls.erase(unchecked_urls.begin(), end); 147 } 148 149 // Return the checked URLs. 150 template_urls->swap(checked_urls); 151 } 152 153 // Returns the TemplateURL with id specified from the list of TemplateURLs. 154 // If not found, returns NULL. 155 TemplateURL* GetTemplateURLByID( 156 const TemplateURLService::TemplateURLVector& template_urls, 157 int64 id) { 158 for (TemplateURLService::TemplateURLVector::const_iterator i( 159 template_urls.begin()); i != template_urls.end(); ++i) { 160 if ((*i)->id() == id) { 161 return *i; 162 } 163 } 164 return NULL; 165 } 166 167 TemplateURL* FindURLByPrepopulateID( 168 const TemplateURLService::TemplateURLVector& template_urls, 169 int prepopulate_id) { 170 for (std::vector<TemplateURL*>::const_iterator i = template_urls.begin(); 171 i < template_urls.end(); ++i) { 172 if ((*i)->prepopulate_id() == prepopulate_id) 173 return *i; 174 } 175 return NULL; 176 } 177 178 void MergeIntoPrepopulatedEngineData(const TemplateURL* original_turl, 179 TemplateURLData* prepopulated_url) { 180 DCHECK_EQ(original_turl->prepopulate_id(), prepopulated_url->prepopulate_id); 181 if (!original_turl->safe_for_autoreplace()) { 182 prepopulated_url->safe_for_autoreplace = false; 183 prepopulated_url->SetKeyword(original_turl->keyword()); 184 prepopulated_url->short_name = original_turl->short_name(); 185 } 186 prepopulated_url->id = original_turl->id(); 187 prepopulated_url->sync_guid = original_turl->sync_guid(); 188 prepopulated_url->date_created = original_turl->date_created(); 189 prepopulated_url->last_modified = original_turl->last_modified(); 190 } 191 192 ActionsFromPrepopulateData::ActionsFromPrepopulateData() {} 193 194 ActionsFromPrepopulateData::~ActionsFromPrepopulateData() {} 195 196 // This is invoked when the version of the prepopulate data changes. 197 // If |removed_keyword_guids| is not NULL, the Sync GUID of each item removed 198 // from the DB will be added to it. Note that this function will take 199 // ownership of |prepopulated_urls| and will clear the vector. 200 void MergeEnginesFromPrepopulateData( 201 Profile* profile, 202 WebDataService* service, 203 ScopedVector<TemplateURLData>* prepopulated_urls, 204 size_t default_search_index, 205 TemplateURLService::TemplateURLVector* template_urls, 206 TemplateURL* default_search_provider, 207 std::set<std::string>* removed_keyword_guids) { 208 DCHECK(service == NULL || BrowserThread::CurrentlyOn(BrowserThread::UI)); 209 DCHECK(prepopulated_urls); 210 DCHECK(template_urls); 211 212 ActionsFromPrepopulateData actions(CreateActionsFromCurrentPrepopulateData( 213 prepopulated_urls, *template_urls, default_search_provider)); 214 215 // Remove items. 216 for (std::vector<TemplateURL*>::iterator i = actions.removed_engines.begin(); 217 i < actions.removed_engines.end(); ++i) { 218 scoped_ptr<TemplateURL> template_url(*i); 219 TemplateURLService::TemplateURLVector::iterator j = 220 std::find(template_urls->begin(), template_urls->end(), template_url); 221 DCHECK(j != template_urls->end()); 222 DCHECK(!default_search_provider || 223 (*j)->prepopulate_id() != default_search_provider->prepopulate_id()); 224 template_urls->erase(j); 225 if (service) { 226 service->RemoveKeyword(template_url->id()); 227 if (removed_keyword_guids) 228 removed_keyword_guids->insert(template_url->sync_guid()); 229 } 230 } 231 232 // Edit items. 233 for (EditedEngines::iterator i(actions.edited_engines.begin()); 234 i < actions.edited_engines.end(); ++i) { 235 TemplateURLData& data = i->second; 236 scoped_ptr<TemplateURL> existing_url(i->first); 237 if (service) 238 service->UpdateKeyword(data); 239 240 // Replace the entry in |template_urls| with the updated one. 241 TemplateURLService::TemplateURLVector::iterator j = std::find( 242 template_urls->begin(), template_urls->end(), existing_url.get()); 243 *j = new TemplateURL(data); 244 } 245 246 // Add items. 247 for (std::vector<TemplateURLData>::const_iterator it = 248 actions.added_engines.begin(); 249 it != actions.added_engines.end(); 250 ++it) { 251 template_urls->push_back(new TemplateURL(*it)); 252 } 253 } 254 255 ActionsFromPrepopulateData CreateActionsFromCurrentPrepopulateData( 256 ScopedVector<TemplateURLData>* prepopulated_urls, 257 const TemplateURLService::TemplateURLVector& existing_urls, 258 const TemplateURL* default_search_provider) { 259 // Create a map to hold all provided |template_urls| that originally came from 260 // prepopulate data (i.e. have a non-zero prepopulate_id()). 261 typedef std::map<int, TemplateURL*> IDMap; 262 IDMap id_to_turl; 263 for (TemplateURLService::TemplateURLVector::const_iterator i( 264 existing_urls.begin()); i != existing_urls.end(); ++i) { 265 int prepopulate_id = (*i)->prepopulate_id(); 266 if (prepopulate_id > 0) 267 id_to_turl[prepopulate_id] = *i; 268 } 269 270 // For each current prepopulated URL, check whether |template_urls| contained 271 // a matching prepopulated URL. If so, update the passed-in URL to match the 272 // current data. (If the passed-in URL was user-edited, we persist the user's 273 // name and keyword.) If not, add the prepopulated URL. 274 ActionsFromPrepopulateData actions; 275 for (size_t i = 0; i < prepopulated_urls->size(); ++i) { 276 // We take ownership of |prepopulated_urls[i]|. 277 scoped_ptr<TemplateURLData> prepopulated_url((*prepopulated_urls)[i]); 278 const int prepopulated_id = prepopulated_url->prepopulate_id; 279 DCHECK_NE(0, prepopulated_id); 280 281 IDMap::iterator existing_url_iter(id_to_turl.find(prepopulated_id)); 282 if (existing_url_iter != id_to_turl.end()) { 283 // Update the data store with the new prepopulated data. Preserve user 284 // edits to the name and keyword. 285 TemplateURL* existing_url(existing_url_iter->second); 286 id_to_turl.erase(existing_url_iter); 287 MergeIntoPrepopulatedEngineData(existing_url, prepopulated_url.get()); 288 // Update last_modified to ensure that if this entry is later merged with 289 // entries from Sync, the conflict resolution logic knows that this was 290 // updated and propagates the new values to the server. 291 prepopulated_url->last_modified = base::Time::Now(); 292 actions.edited_engines.push_back( 293 std::make_pair(existing_url, *prepopulated_url)); 294 } else { 295 actions.added_engines.push_back(*prepopulated_url); 296 } 297 } 298 // The above loop takes ownership of all the contents of prepopulated_urls. 299 // Clear the pointers. 300 prepopulated_urls->weak_erase(prepopulated_urls->begin(), 301 prepopulated_urls->end()); 302 303 // The block above removed all the URLs from the |id_to_turl| map that were 304 // found in the prepopulate data. Any remaining URLs that haven't been 305 // user-edited or made default can be removed from the data store. 306 // We assume that this entry is equivalent to the DSE if its prepopulate ID 307 // and keyword both match. If the prepopulate ID _does_ match all properties 308 // will be replaced with those from |default_search_provider| anyway. 309 for (IDMap::iterator i(id_to_turl.begin()); i != id_to_turl.end(); ++i) { 310 TemplateURL* template_url = i->second; 311 if ((template_url->safe_for_autoreplace()) && 312 (!default_search_provider || 313 (template_url->prepopulate_id() != 314 default_search_provider->prepopulate_id()) || 315 (template_url->keyword() != default_search_provider->keyword()))) 316 actions.removed_engines.push_back(template_url); 317 } 318 319 return actions; 320 } 321 322 void GetSearchProvidersUsingKeywordResult( 323 const WDTypedResult& result, 324 WebDataService* service, 325 Profile* profile, 326 TemplateURLService::TemplateURLVector* template_urls, 327 TemplateURL* default_search_provider, 328 const SearchTermsData& search_terms_data, 329 int* new_resource_keyword_version, 330 std::set<std::string>* removed_keyword_guids) { 331 DCHECK(service == NULL || BrowserThread::CurrentlyOn(BrowserThread::UI)); 332 DCHECK(template_urls); 333 DCHECK(template_urls->empty()); 334 DCHECK_EQ(KEYWORDS_RESULT, result.GetType()); 335 DCHECK(new_resource_keyword_version); 336 337 WDKeywordsResult keyword_result = reinterpret_cast< 338 const WDResult<WDKeywordsResult>*>(&result)->GetValue(); 339 340 for (KeywordTable::Keywords::iterator i(keyword_result.keywords.begin()); 341 i != keyword_result.keywords.end(); ++i) { 342 // Fix any duplicate encodings in the local database. Note that we don't 343 // adjust the last_modified time of this keyword; this way, we won't later 344 // overwrite any changes on the sync server that happened to this keyword 345 // since the last time we synced. Instead, we also run a de-duping pass on 346 // the server-provided data in 347 // TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData() and 348 // update the server with the merged, de-duped results at that time. We 349 // still fix here, though, to correct problems in clients that have disabled 350 // search engine sync, since in that case that code will never be reached. 351 if (DeDupeEncodings(&i->input_encodings) && service) 352 service->UpdateKeyword(*i); 353 template_urls->push_back(new TemplateURL(*i)); 354 } 355 356 *new_resource_keyword_version = keyword_result.builtin_keyword_version; 357 GetSearchProvidersUsingLoadedEngines(service, profile, template_urls, 358 default_search_provider, 359 search_terms_data, 360 new_resource_keyword_version, 361 removed_keyword_guids); 362 } 363 364 void GetSearchProvidersUsingLoadedEngines( 365 WebDataService* service, 366 Profile* profile, 367 TemplateURLService::TemplateURLVector* template_urls, 368 TemplateURL* default_search_provider, 369 const SearchTermsData& search_terms_data, 370 int* resource_keyword_version, 371 std::set<std::string>* removed_keyword_guids) { 372 DCHECK(service == NULL || BrowserThread::CurrentlyOn(BrowserThread::UI)); 373 DCHECK(template_urls); 374 DCHECK(resource_keyword_version); 375 PrefService* prefs = profile ? profile->GetPrefs() : NULL; 376 size_t default_search_index; 377 ScopedVector<TemplateURLData> prepopulated_urls = 378 TemplateURLPrepopulateData::GetPrepopulatedEngines(prefs, 379 &default_search_index); 380 RemoveDuplicatePrepopulateIDs(service, prepopulated_urls, 381 default_search_provider, template_urls, 382 search_terms_data, removed_keyword_guids); 383 384 const int prepopulate_resource_keyword_version = 385 TemplateURLPrepopulateData::GetDataVersion(prefs); 386 if (*resource_keyword_version < prepopulate_resource_keyword_version) { 387 MergeEnginesFromPrepopulateData(profile, service, &prepopulated_urls, 388 default_search_index, template_urls, default_search_provider, 389 removed_keyword_guids); 390 *resource_keyword_version = prepopulate_resource_keyword_version; 391 } else { 392 *resource_keyword_version = 0; 393 } 394 } 395 396 bool DeDupeEncodings(std::vector<std::string>* encodings) { 397 std::vector<std::string> deduped_encodings; 398 std::set<std::string> encoding_set; 399 for (std::vector<std::string>::const_iterator i(encodings->begin()); 400 i != encodings->end(); ++i) { 401 if (encoding_set.insert(*i).second) 402 deduped_encodings.push_back(*i); 403 } 404 encodings->swap(deduped_encodings); 405 return encodings->size() != deduped_encodings.size(); 406 } 407