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