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