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 "build/build_config.h" 6 7 #include "chrome/browser/search_engines/template_url_fetcher.h" 8 9 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/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_parser.h" 15 #include "chrome/browser/search_engines/template_url_service.h" 16 #include "chrome/browser/search_engines/template_url_service_factory.h" 17 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h" 18 #include "content/public/browser/render_frame_host.h" 19 #include "content/public/browser/render_process_host.h" 20 #include "content/public/browser/web_contents.h" 21 #include "content/public/common/url_fetcher.h" 22 #include "net/base/load_flags.h" 23 #include "net/url_request/url_fetcher.h" 24 #include "net/url_request/url_fetcher_delegate.h" 25 #include "net/url_request/url_request_status.h" 26 27 // RequestDelegate ------------------------------------------------------------ 28 class TemplateURLFetcher::RequestDelegate : public net::URLFetcherDelegate { 29 public: 30 // Takes ownership of |callbacks|. 31 RequestDelegate(TemplateURLFetcher* fetcher, 32 const base::string16& keyword, 33 const GURL& osdd_url, 34 const GURL& favicon_url, 35 content::WebContents* web_contents, 36 TemplateURLFetcherCallbacks* callbacks, 37 ProviderType provider_type); 38 39 // net::URLFetcherDelegate: 40 // If data contains a valid OSDD, a TemplateURL is created and added to 41 // the TemplateURLService. 42 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; 43 44 // URL of the OSDD. 45 GURL url() const { return osdd_url_; } 46 47 // Keyword to use. 48 base::string16 keyword() const { return keyword_; } 49 50 // The type of search provider being fetched. 51 ProviderType provider_type() const { return provider_type_; } 52 53 private: 54 void OnLoaded(); 55 void AddSearchProvider(); 56 57 scoped_ptr<net::URLFetcher> url_fetcher_; 58 TemplateURLFetcher* fetcher_; 59 scoped_ptr<TemplateURL> template_url_; 60 base::string16 keyword_; 61 const GURL osdd_url_; 62 const GURL favicon_url_; 63 const ProviderType provider_type_; 64 scoped_ptr<TemplateURLFetcherCallbacks> callbacks_; 65 66 scoped_ptr<TemplateURLService::Subscription> template_url_subscription_; 67 68 DISALLOW_COPY_AND_ASSIGN(RequestDelegate); 69 }; 70 71 TemplateURLFetcher::RequestDelegate::RequestDelegate( 72 TemplateURLFetcher* fetcher, 73 const base::string16& keyword, 74 const GURL& osdd_url, 75 const GURL& favicon_url, 76 content::WebContents* web_contents, 77 TemplateURLFetcherCallbacks* callbacks, 78 ProviderType provider_type) 79 : url_fetcher_(net::URLFetcher::Create( 80 osdd_url, net::URLFetcher::GET, this)), 81 fetcher_(fetcher), 82 keyword_(keyword), 83 osdd_url_(osdd_url), 84 favicon_url_(favicon_url), 85 provider_type_(provider_type), 86 callbacks_(callbacks) { 87 TemplateURLService* model = TemplateURLServiceFactory::GetForProfile( 88 fetcher_->profile()); 89 DCHECK(model); // TemplateURLFetcher::ScheduleDownload verifies this. 90 91 if (!model->loaded()) { 92 // Start the model load and set-up waiting for it. 93 template_url_subscription_ = model->RegisterOnLoadedCallback( 94 base::Bind(&TemplateURLFetcher::RequestDelegate::OnLoaded, 95 base::Unretained(this))); 96 model->Load(); 97 } 98 99 url_fetcher_->SetRequestContext(fetcher->profile()->GetRequestContext()); 100 // Can be NULL during tests. 101 if (web_contents) { 102 content::AssociateURLFetcherWithRenderFrame( 103 url_fetcher_.get(), 104 web_contents->GetURL(), 105 web_contents->GetRenderProcessHost()->GetID(), 106 web_contents->GetMainFrame()->GetRoutingID()); 107 } 108 109 url_fetcher_->Start(); 110 } 111 112 void TemplateURLFetcher::RequestDelegate::OnLoaded() { 113 template_url_subscription_.reset(); 114 if (!template_url_.get()) 115 return; 116 AddSearchProvider(); 117 // WARNING: AddSearchProvider deletes us. 118 } 119 120 void TemplateURLFetcher::RequestDelegate::OnURLFetchComplete( 121 const net::URLFetcher* source) { 122 // Validation checks. 123 // Make sure we can still replace the keyword, i.e. the fetch was successful. 124 // If the OSDD file was loaded HTTP, we also have to check the response_code. 125 // For other schemes, e.g. when the OSDD file is bundled with an extension, 126 // the response_code is not applicable and should be -1. Also, ensure that 127 // the returned information results in a valid search URL. 128 std::string data; 129 if (!source->GetStatus().is_success() || 130 ((source->GetResponseCode() != -1) && 131 (source->GetResponseCode() != 200)) || 132 !source->GetResponseAsString(&data)) { 133 fetcher_->RequestCompleted(this); 134 // WARNING: RequestCompleted deletes us. 135 return; 136 } 137 138 template_url_.reset(TemplateURLParser::Parse(fetcher_->profile(), false, 139 data.data(), data.length(), NULL)); 140 if (!template_url_.get() || 141 !template_url_->url_ref().SupportsReplacement( 142 UIThreadSearchTermsData(fetcher_->profile()))) { 143 fetcher_->RequestCompleted(this); 144 // WARNING: RequestCompleted deletes us. 145 return; 146 } 147 148 if (provider_type_ != AUTODETECTED_PROVIDER || keyword_.empty()) { 149 // Use the parser-generated new keyword from the URL in the OSDD for the 150 // non-autodetected case. The existing |keyword_| was generated from the 151 // URL that hosted the OSDD, which results in the wrong keyword when the 152 // OSDD was located on a third-party site that has nothing in common with 153 // search engine described by OSDD. 154 keyword_ = template_url_->keyword(); 155 DCHECK(!keyword_.empty()); 156 } 157 158 // Wait for the model to be loaded before adding the provider. 159 TemplateURLService* model = TemplateURLServiceFactory::GetForProfile( 160 fetcher_->profile()); 161 if (!model->loaded()) 162 return; 163 AddSearchProvider(); 164 // WARNING: AddSearchProvider deletes us. 165 } 166 167 void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { 168 DCHECK(template_url_.get()); 169 DCHECK(!keyword_.empty()); 170 Profile* profile = fetcher_->profile(); 171 TemplateURLService* model = TemplateURLServiceFactory::GetForProfile(profile); 172 DCHECK(model); 173 DCHECK(model->loaded()); 174 175 TemplateURL* existing_url = NULL; 176 if (model->CanReplaceKeyword(keyword_, GURL(template_url_->url()), 177 &existing_url)) { 178 if (existing_url) 179 model->Remove(existing_url); 180 } else if (provider_type_ == AUTODETECTED_PROVIDER) { 181 fetcher_->RequestCompleted(this); // WARNING: Deletes us! 182 return; 183 } 184 185 // The short name is what is shown to the user. We preserve original names 186 // since it is better when generated keyword in many cases. 187 TemplateURLData data(template_url_->data()); 188 data.SetKeyword(keyword_); 189 data.originating_url = osdd_url_; 190 191 // The page may have specified a URL to use for favicons, if not, set it. 192 if (!data.favicon_url.is_valid()) 193 data.favicon_url = favicon_url_; 194 195 switch (provider_type_) { 196 case AUTODETECTED_PROVIDER: 197 // Mark the keyword as replaceable so it can be removed if necessary. 198 data.safe_for_autoreplace = true; 199 model->Add(new TemplateURL(data)); 200 break; 201 202 case EXPLICIT_PROVIDER: 203 // Confirm addition and allow user to edit default choices. It's ironic 204 // that only *non*-autodetected additions get confirmed, but the user 205 // expects feedback that his action did something. 206 // The source WebContents' delegate takes care of adding the URL to the 207 // model, which takes ownership, or of deleting it if the add is 208 // cancelled. 209 callbacks_->ConfirmAddSearchProvider(new TemplateURL(data), profile); 210 break; 211 212 default: 213 NOTREACHED(); 214 break; 215 } 216 217 fetcher_->RequestCompleted(this); 218 // WARNING: RequestCompleted deletes us. 219 } 220 221 // TemplateURLFetcher --------------------------------------------------------- 222 223 TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) { 224 DCHECK(profile_); 225 } 226 227 TemplateURLFetcher::~TemplateURLFetcher() { 228 } 229 230 void TemplateURLFetcher::ScheduleDownload( 231 const base::string16& keyword, 232 const GURL& osdd_url, 233 const GURL& favicon_url, 234 content::WebContents* web_contents, 235 TemplateURLFetcherCallbacks* callbacks, 236 ProviderType provider_type) { 237 DCHECK(osdd_url.is_valid()); 238 scoped_ptr<TemplateURLFetcherCallbacks> owned_callbacks(callbacks); 239 240 TemplateURLService* url_model = 241 TemplateURLServiceFactory::GetForProfile(profile()); 242 if (!url_model) 243 return; 244 245 // For a JS-added OSDD, the provided keyword is irrelevant because we will 246 // generate a keyword later from the OSDD content. For the autodetected case, 247 // we need a valid keyword up front. 248 if (provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER) { 249 DCHECK(!keyword.empty()); 250 251 if (!url_model->loaded()) { 252 // We could try to set up a callback to this function again once the model 253 // is loaded but since this is an auto-add case anyway, meh. 254 url_model->Load(); 255 return; 256 } 257 258 const TemplateURL* template_url = 259 url_model->GetTemplateURLForKeyword(keyword); 260 if (template_url && (!template_url->safe_for_autoreplace() || 261 template_url->originating_url() == osdd_url)) 262 return; 263 } 264 265 // Make sure we aren't already downloading this request. 266 for (Requests::iterator i = requests_.begin(); i != requests_.end(); ++i) { 267 if (((*i)->url() == osdd_url) || 268 ((provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER) && 269 ((*i)->keyword() == keyword))) 270 return; 271 } 272 273 requests_.push_back( 274 new RequestDelegate(this, keyword, osdd_url, favicon_url, web_contents, 275 owned_callbacks.release(), provider_type)); 276 } 277 278 void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) { 279 Requests::iterator i = 280 std::find(requests_.begin(), requests_.end(), request); 281 DCHECK(i != requests_.end()); 282 requests_.weak_erase(i); 283 delete request; 284 } 285