Home | History | Annotate | Download | only in search_engines
      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 ------------------------------------------------------------
     21 class 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 
     64 TemplateURLFetcher::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 
     98 void TemplateURLFetcher::RequestDelegate::OnLoaded() {
     99   template_url_subscription_.reset();
    100   if (!template_url_.get())
    101     return;
    102   AddSearchProvider();
    103   // WARNING: AddSearchProvider deletes us.
    104 }
    105 
    106 void 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 
    152 void 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 
    207 TemplateURLFetcher::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 
    214 TemplateURLFetcher::~TemplateURLFetcher() {
    215 }
    216 
    217 void 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 
    259 void 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