1 // Copyright (c) 2011 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 "build/build_config.h" 6 7 #include "chrome/browser/search_engines/template_url_fetcher.h" 8 9 #include "base/string_number_conversions.h" 10 #include "base/utf_string_conversions.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/search_engines/template_url.h" 13 #include "chrome/browser/search_engines/template_url_fetcher_callbacks.h" 14 #include "chrome/browser/search_engines/template_url_model.h" 15 #include "chrome/browser/search_engines/template_url_parser.h" 16 #include "chrome/common/net/url_fetcher.h" 17 #include "content/common/notification_observer.h" 18 #include "content/common/notification_registrar.h" 19 #include "content/common/notification_source.h" 20 #include "content/common/notification_type.h" 21 #include "net/url_request/url_request_status.h" 22 23 // RequestDelegate ------------------------------------------------------------ 24 class TemplateURLFetcher::RequestDelegate : public URLFetcher::Delegate, 25 public NotificationObserver { 26 public: 27 // Takes ownership of |callbacks|. 28 RequestDelegate(TemplateURLFetcher* fetcher, 29 const string16& keyword, 30 const GURL& osdd_url, 31 const GURL& favicon_url, 32 TemplateURLFetcherCallbacks* callbacks, 33 ProviderType provider_type); 34 35 // NotificationObserver: 36 virtual void Observe(NotificationType type, 37 const NotificationSource& source, 38 const NotificationDetails& details); 39 40 // URLFetcher::Delegate: 41 // If data contains a valid OSDD, a TemplateURL is created and added to 42 // the TemplateURLModel. 43 virtual void OnURLFetchComplete(const URLFetcher* source, 44 const GURL& url, 45 const net::URLRequestStatus& status, 46 int response_code, 47 const ResponseCookies& cookies, 48 const std::string& data); 49 50 // URL of the OSDD. 51 GURL url() const { return osdd_url_; } 52 53 // Keyword to use. 54 string16 keyword() const { return keyword_; } 55 56 // The type of search provider being fetched. 57 ProviderType provider_type() const { return provider_type_; } 58 59 private: 60 void AddSearchProvider(); 61 62 URLFetcher url_fetcher_; 63 TemplateURLFetcher* fetcher_; 64 scoped_ptr<TemplateURL> template_url_; 65 string16 keyword_; 66 const GURL osdd_url_; 67 const GURL favicon_url_; 68 const ProviderType provider_type_; 69 scoped_ptr<TemplateURLFetcherCallbacks> callbacks_; 70 71 // Handles registering for our notifications. 72 NotificationRegistrar registrar_; 73 74 DISALLOW_COPY_AND_ASSIGN(RequestDelegate); 75 }; 76 77 TemplateURLFetcher::RequestDelegate::RequestDelegate( 78 TemplateURLFetcher* fetcher, 79 const string16& keyword, 80 const GURL& osdd_url, 81 const GURL& favicon_url, 82 TemplateURLFetcherCallbacks* callbacks, 83 ProviderType provider_type) 84 : ALLOW_THIS_IN_INITIALIZER_LIST(url_fetcher_(osdd_url, 85 URLFetcher::GET, this)), 86 fetcher_(fetcher), 87 keyword_(keyword), 88 osdd_url_(osdd_url), 89 favicon_url_(favicon_url), 90 provider_type_(provider_type), 91 callbacks_(callbacks) { 92 TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel(); 93 DCHECK(model); // TemplateURLFetcher::ScheduleDownload verifies this. 94 95 if (!model->loaded()) { 96 // Start the model load and set-up waiting for it. 97 registrar_.Add(this, 98 NotificationType::TEMPLATE_URL_MODEL_LOADED, 99 Source<TemplateURLModel>(model)); 100 model->Load(); 101 } 102 103 url_fetcher_.set_request_context(fetcher->profile()->GetRequestContext()); 104 url_fetcher_.Start(); 105 } 106 107 void TemplateURLFetcher::RequestDelegate::Observe( 108 NotificationType type, 109 const NotificationSource& source, 110 const NotificationDetails& details) { 111 DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED); 112 113 if (!template_url_.get()) 114 return; 115 AddSearchProvider(); 116 // WARNING: AddSearchProvider deletes us. 117 } 118 119 void TemplateURLFetcher::RequestDelegate::OnURLFetchComplete( 120 const URLFetcher* source, 121 const GURL& url, 122 const net::URLRequestStatus& status, 123 int response_code, 124 const ResponseCookies& cookies, 125 const std::string& data) { 126 template_url_.reset(new TemplateURL()); 127 128 // Validation checks. 129 // Make sure we can still replace the keyword, i.e. the fetch was successful. 130 // If the OSDD file was loaded HTTP, we also have to check the response_code. 131 // For other schemes, e.g. when the OSDD file is bundled with an extension, 132 // the response_code is not applicable and should be -1. Also, ensure that 133 // the returned information results in a valid search URL. 134 if (!status.is_success() || 135 ((response_code != -1) && (response_code != 200)) || 136 !TemplateURLParser::Parse( 137 reinterpret_cast<const unsigned char*>(data.c_str()), 138 data.length(), 139 NULL, 140 template_url_.get()) || 141 !template_url_->url() || !template_url_->url()->SupportsReplacement()) { 142 fetcher_->RequestCompleted(this); 143 // WARNING: RequestCompleted deletes us. 144 return; 145 } 146 147 // Wait for the model to be loaded before adding the provider. 148 TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel(); 149 if (!model->loaded()) 150 return; 151 AddSearchProvider(); 152 // WARNING: AddSearchProvider deletes us. 153 } 154 155 void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { 156 DCHECK(template_url_.get()); 157 if (provider_type_ != AUTODETECTED_PROVIDER || keyword_.empty()) { 158 // Generate new keyword from URL in OSDD for none autodetected case. 159 // Previous keyword was generated from URL where OSDD was placed and 160 // it gives wrong result when OSDD is located on third party site that 161 // has nothing in common with search engine in OSDD. 162 GURL keyword_url(template_url_->url()->url()); 163 string16 new_keyword = TemplateURLModel::GenerateKeyword( 164 keyword_url, false); 165 if (!new_keyword.empty()) 166 keyword_ = new_keyword; 167 } 168 TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel(); 169 const TemplateURL* existing_url; 170 if (keyword_.empty() || 171 !model || !model->loaded() || 172 !model->CanReplaceKeyword(keyword_, GURL(template_url_->url()->url()), 173 &existing_url)) { 174 if (provider_type_ == AUTODETECTED_PROVIDER || !model || !model->loaded()) { 175 fetcher_->RequestCompleted(this); 176 // WARNING: RequestCompleted deletes us. 177 return; 178 } 179 180 existing_url = NULL; 181 182 // Try to generate a keyword automatically when we are setting the default 183 // provider. The keyword isn't as important in this case. 184 if (provider_type_ == EXPLICIT_DEFAULT_PROVIDER) { 185 // The loop numbers are arbitrary and are simply a strong effort. 186 string16 new_keyword; 187 for (int i = 0; i < 100; ++i) { 188 // Concatenate a number at end of the keyword and try that. 189 new_keyword = keyword_; 190 // Try the keyword alone the first time 191 if (i > 0) 192 new_keyword.append(base::IntToString16(i)); 193 if (!model->GetTemplateURLForKeyword(new_keyword) || 194 model->CanReplaceKeyword(new_keyword, 195 GURL(template_url_->url()->url()), 196 &existing_url)) { 197 break; 198 } 199 new_keyword.clear(); 200 existing_url = NULL; 201 } 202 203 if (new_keyword.empty()) { 204 // A keyword could not be found. This user must have a lot of numerical 205 // keywords built up. 206 fetcher_->RequestCompleted(this); 207 // WARNING: RequestCompleted deletes us. 208 return; 209 } 210 keyword_ = new_keyword; 211 } else { 212 // If we're coming from JS (neither autodetected nor failure to load the 213 // template URL model) and this URL already exists in the model, we bring 214 // up the EditKeywordController to edit it. This is helpful feedback in 215 // the case of clicking a button twice, and annoying in the case of a 216 // page that calls AddSearchProvider() in JS without a user action. 217 keyword_.clear(); 218 } 219 } 220 221 if (existing_url) 222 model->Remove(existing_url); 223 224 // The short name is what is shown to the user. We preserve original names 225 // since it is better when generated keyword in many cases. 226 template_url_->set_keyword(keyword_); 227 template_url_->set_originating_url(osdd_url_); 228 229 // The page may have specified a URL to use for favicons, if not, set it. 230 if (!template_url_->GetFaviconURL().is_valid()) 231 template_url_->SetFaviconURL(favicon_url_); 232 233 switch (provider_type_) { 234 case AUTODETECTED_PROVIDER: 235 // Mark the keyword as replaceable so it can be removed if necessary. 236 template_url_->set_safe_for_autoreplace(true); 237 model->Add(template_url_.release()); 238 break; 239 240 case EXPLICIT_PROVIDER: 241 // Confirm addition and allow user to edit default choices. It's ironic 242 // that only *non*-autodetected additions get confirmed, but the user 243 // expects feedback that his action did something. 244 // The source TabContents' delegate takes care of adding the URL to the 245 // model, which takes ownership, or of deleting it if the add is 246 // cancelled. 247 callbacks_->ConfirmAddSearchProvider(template_url_.release(), 248 fetcher_->profile()); 249 break; 250 251 case EXPLICIT_DEFAULT_PROVIDER: 252 callbacks_->ConfirmSetDefaultSearchProvider(template_url_.release(), 253 model); 254 break; 255 } 256 257 fetcher_->RequestCompleted(this); 258 // WARNING: RequestCompleted deletes us. 259 } 260 261 // TemplateURLFetcher --------------------------------------------------------- 262 263 TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) { 264 DCHECK(profile_); 265 } 266 267 TemplateURLFetcher::~TemplateURLFetcher() { 268 } 269 270 void TemplateURLFetcher::ScheduleDownload( 271 const string16& keyword, 272 const GURL& osdd_url, 273 const GURL& favicon_url, 274 TemplateURLFetcherCallbacks* callbacks, 275 ProviderType provider_type) { 276 DCHECK(osdd_url.is_valid()); 277 scoped_ptr<TemplateURLFetcherCallbacks> owned_callbacks(callbacks); 278 279 // For JS added OSDD empty keyword is OK because we will generate keyword 280 // later from OSDD content. 281 if (provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER && 282 keyword.empty()) 283 return; 284 TemplateURLModel* url_model = profile()->GetTemplateURLModel(); 285 if (!url_model) 286 return; 287 288 // Avoid certain checks for the default provider because we'll do the load 289 // and try to brute force a unique keyword for it. 290 if (provider_type != TemplateURLFetcher::EXPLICIT_DEFAULT_PROVIDER) { 291 if (!url_model->loaded()) { 292 url_model->Load(); 293 return; 294 } 295 const TemplateURL* template_url = 296 url_model->GetTemplateURLForKeyword(keyword); 297 if (template_url && (!template_url->safe_for_autoreplace() || 298 template_url->originating_url() == osdd_url)) { 299 // Either there is a user created TemplateURL for this keyword, or the 300 // keyword has the same OSDD url and we've parsed it. 301 return; 302 } 303 } 304 305 // Make sure we aren't already downloading this request. 306 for (std::vector<RequestDelegate*>::iterator i = requests_->begin(); 307 i != requests_->end(); ++i) { 308 bool keyword_or_osdd_match = (*i)->url() == osdd_url || 309 (*i)->keyword() == keyword; 310 bool same_type_or_neither_is_default = 311 (*i)->provider_type() == provider_type || 312 ((*i)->provider_type() != EXPLICIT_DEFAULT_PROVIDER && 313 provider_type != EXPLICIT_DEFAULT_PROVIDER); 314 if (keyword_or_osdd_match && same_type_or_neither_is_default) 315 return; 316 } 317 318 requests_->push_back( 319 new RequestDelegate(this, keyword, osdd_url, favicon_url, 320 owned_callbacks.release(), provider_type)); 321 } 322 323 void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) { 324 DCHECK(find(requests_->begin(), requests_->end(), request) != 325 requests_->end()); 326 requests_->erase(find(requests_->begin(), requests_->end(), request)); 327 delete request; 328 } 329