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