Home | History | Annotate | Download | only in search_engines
      1 // Copyright (c) 2011 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/string_number_conversions.h"
     10 #include "base/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_model.h"
     15 #include "chrome/browser/search_engines/template_url_parser.h"
     16 #include "chrome/common/net/url_fetcher.h"
     17 #include "content/common/notification_observer.h"
     18 #include "content/common/notification_registrar.h"
     19 #include "content/common/notification_source.h"
     20 #include "content/common/notification_type.h"
     21 #include "net/url_request/url_request_status.h"
     22 
     23 // RequestDelegate ------------------------------------------------------------
     24 class TemplateURLFetcher::RequestDelegate : public URLFetcher::Delegate,
     25                                             public NotificationObserver {
     26  public:
     27   // Takes ownership of |callbacks|.
     28   RequestDelegate(TemplateURLFetcher* fetcher,
     29                   const string16& keyword,
     30                   const GURL& osdd_url,
     31                   const GURL& favicon_url,
     32                   TemplateURLFetcherCallbacks* callbacks,
     33                   ProviderType provider_type);
     34 
     35   // NotificationObserver:
     36   virtual void Observe(NotificationType type,
     37                        const NotificationSource& source,
     38                        const NotificationDetails& details);
     39 
     40   // URLFetcher::Delegate:
     41   // If data contains a valid OSDD, a TemplateURL is created and added to
     42   // the TemplateURLModel.
     43   virtual void OnURLFetchComplete(const URLFetcher* source,
     44                                   const GURL& url,
     45                                   const net::URLRequestStatus& status,
     46                                   int response_code,
     47                                   const ResponseCookies& cookies,
     48                                   const std::string& data);
     49 
     50   // URL of the OSDD.
     51   GURL url() const { return osdd_url_; }
     52 
     53   // Keyword to use.
     54   string16 keyword() const { return keyword_; }
     55 
     56   // The type of search provider being fetched.
     57   ProviderType provider_type() const { return provider_type_; }
     58 
     59  private:
     60   void AddSearchProvider();
     61 
     62   URLFetcher url_fetcher_;
     63   TemplateURLFetcher* fetcher_;
     64   scoped_ptr<TemplateURL> template_url_;
     65   string16 keyword_;
     66   const GURL osdd_url_;
     67   const GURL favicon_url_;
     68   const ProviderType provider_type_;
     69   scoped_ptr<TemplateURLFetcherCallbacks> callbacks_;
     70 
     71   // Handles registering for our notifications.
     72   NotificationRegistrar registrar_;
     73 
     74   DISALLOW_COPY_AND_ASSIGN(RequestDelegate);
     75 };
     76 
     77 TemplateURLFetcher::RequestDelegate::RequestDelegate(
     78     TemplateURLFetcher* fetcher,
     79     const string16& keyword,
     80     const GURL& osdd_url,
     81     const GURL& favicon_url,
     82     TemplateURLFetcherCallbacks* callbacks,
     83     ProviderType provider_type)
     84     : ALLOW_THIS_IN_INITIALIZER_LIST(url_fetcher_(osdd_url,
     85                                                   URLFetcher::GET, this)),
     86       fetcher_(fetcher),
     87       keyword_(keyword),
     88       osdd_url_(osdd_url),
     89       favicon_url_(favicon_url),
     90       provider_type_(provider_type),
     91       callbacks_(callbacks) {
     92   TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
     93   DCHECK(model);  // TemplateURLFetcher::ScheduleDownload verifies this.
     94 
     95   if (!model->loaded()) {
     96     // Start the model load and set-up waiting for it.
     97     registrar_.Add(this,
     98                    NotificationType::TEMPLATE_URL_MODEL_LOADED,
     99                    Source<TemplateURLModel>(model));
    100     model->Load();
    101   }
    102 
    103   url_fetcher_.set_request_context(fetcher->profile()->GetRequestContext());
    104   url_fetcher_.Start();
    105 }
    106 
    107 void TemplateURLFetcher::RequestDelegate::Observe(
    108     NotificationType type,
    109     const NotificationSource& source,
    110     const NotificationDetails& details) {
    111   DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED);
    112 
    113   if (!template_url_.get())
    114     return;
    115   AddSearchProvider();
    116   // WARNING: AddSearchProvider deletes us.
    117 }
    118 
    119 void TemplateURLFetcher::RequestDelegate::OnURLFetchComplete(
    120     const URLFetcher* source,
    121     const GURL& url,
    122     const net::URLRequestStatus& status,
    123     int response_code,
    124     const ResponseCookies& cookies,
    125     const std::string& data) {
    126   template_url_.reset(new TemplateURL());
    127 
    128   // Validation checks.
    129   // Make sure we can still replace the keyword, i.e. the fetch was successful.
    130   // If the OSDD file was loaded HTTP, we also have to check the response_code.
    131   // For other schemes, e.g. when the OSDD file is bundled with an extension,
    132   // the response_code is not applicable and should be -1. Also, ensure that
    133   // the returned information results in a valid search URL.
    134   if (!status.is_success() ||
    135       ((response_code != -1) && (response_code != 200)) ||
    136       !TemplateURLParser::Parse(
    137           reinterpret_cast<const unsigned char*>(data.c_str()),
    138           data.length(),
    139           NULL,
    140           template_url_.get()) ||
    141       !template_url_->url() || !template_url_->url()->SupportsReplacement()) {
    142     fetcher_->RequestCompleted(this);
    143     // WARNING: RequestCompleted deletes us.
    144     return;
    145   }
    146 
    147   // Wait for the model to be loaded before adding the provider.
    148   TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
    149   if (!model->loaded())
    150     return;
    151   AddSearchProvider();
    152   // WARNING: AddSearchProvider deletes us.
    153 }
    154 
    155 void TemplateURLFetcher::RequestDelegate::AddSearchProvider() {
    156   DCHECK(template_url_.get());
    157   if (provider_type_ != AUTODETECTED_PROVIDER || keyword_.empty()) {
    158     // Generate new keyword from URL in OSDD for none autodetected case.
    159     // Previous keyword was generated from URL where OSDD was placed and
    160     // it gives wrong result when OSDD is located on third party site that
    161     // has nothing in common with search engine in OSDD.
    162     GURL keyword_url(template_url_->url()->url());
    163     string16 new_keyword = TemplateURLModel::GenerateKeyword(
    164         keyword_url, false);
    165     if (!new_keyword.empty())
    166       keyword_ = new_keyword;
    167   }
    168   TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
    169   const TemplateURL* existing_url;
    170   if (keyword_.empty() ||
    171       !model || !model->loaded() ||
    172       !model->CanReplaceKeyword(keyword_, GURL(template_url_->url()->url()),
    173                                 &existing_url)) {
    174     if (provider_type_ == AUTODETECTED_PROVIDER || !model || !model->loaded()) {
    175       fetcher_->RequestCompleted(this);
    176       // WARNING: RequestCompleted deletes us.
    177       return;
    178     }
    179 
    180     existing_url = NULL;
    181 
    182     // Try to generate a keyword automatically when we are setting the default
    183     // provider. The keyword isn't as important in this case.
    184     if (provider_type_ == EXPLICIT_DEFAULT_PROVIDER) {
    185       // The loop numbers are arbitrary and are simply a strong effort.
    186       string16 new_keyword;
    187       for (int i = 0; i < 100; ++i) {
    188         // Concatenate a number at end of the keyword and try that.
    189         new_keyword = keyword_;
    190         // Try the keyword alone the first time
    191         if (i > 0)
    192           new_keyword.append(base::IntToString16(i));
    193         if (!model->GetTemplateURLForKeyword(new_keyword) ||
    194             model->CanReplaceKeyword(new_keyword,
    195                                      GURL(template_url_->url()->url()),
    196                                      &existing_url)) {
    197           break;
    198         }
    199         new_keyword.clear();
    200         existing_url = NULL;
    201       }
    202 
    203       if (new_keyword.empty()) {
    204         // A keyword could not be found. This user must have a lot of numerical
    205         // keywords built up.
    206         fetcher_->RequestCompleted(this);
    207         // WARNING: RequestCompleted deletes us.
    208         return;
    209       }
    210       keyword_ = new_keyword;
    211     } else {
    212       // If we're coming from JS (neither autodetected nor failure to load the
    213       // template URL model) and this URL already exists in the model, we bring
    214       // up the EditKeywordController to edit it.  This is helpful feedback in
    215       // the case of clicking a button twice, and annoying in the case of a
    216       // page that calls AddSearchProvider() in JS without a user action.
    217       keyword_.clear();
    218     }
    219   }
    220 
    221   if (existing_url)
    222     model->Remove(existing_url);
    223 
    224   // The short name is what is shown to the user. We preserve original names
    225   // since it is better when generated keyword in many cases.
    226   template_url_->set_keyword(keyword_);
    227   template_url_->set_originating_url(osdd_url_);
    228 
    229   // The page may have specified a URL to use for favicons, if not, set it.
    230   if (!template_url_->GetFaviconURL().is_valid())
    231     template_url_->SetFaviconURL(favicon_url_);
    232 
    233   switch (provider_type_) {
    234     case AUTODETECTED_PROVIDER:
    235       // Mark the keyword as replaceable so it can be removed if necessary.
    236       template_url_->set_safe_for_autoreplace(true);
    237       model->Add(template_url_.release());
    238       break;
    239 
    240     case EXPLICIT_PROVIDER:
    241       // Confirm addition and allow user to edit default choices. It's ironic
    242       // that only *non*-autodetected additions get confirmed, but the user
    243       // expects feedback that his action did something.
    244       // The source TabContents' delegate takes care of adding the URL to the
    245       // model, which takes ownership, or of deleting it if the add is
    246       // cancelled.
    247       callbacks_->ConfirmAddSearchProvider(template_url_.release(),
    248                                            fetcher_->profile());
    249       break;
    250 
    251     case EXPLICIT_DEFAULT_PROVIDER:
    252       callbacks_->ConfirmSetDefaultSearchProvider(template_url_.release(),
    253                                                   model);
    254       break;
    255   }
    256 
    257   fetcher_->RequestCompleted(this);
    258   // WARNING: RequestCompleted deletes us.
    259 }
    260 
    261 // TemplateURLFetcher ---------------------------------------------------------
    262 
    263 TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) {
    264   DCHECK(profile_);
    265 }
    266 
    267 TemplateURLFetcher::~TemplateURLFetcher() {
    268 }
    269 
    270 void TemplateURLFetcher::ScheduleDownload(
    271     const string16& keyword,
    272     const GURL& osdd_url,
    273     const GURL& favicon_url,
    274     TemplateURLFetcherCallbacks* callbacks,
    275     ProviderType provider_type) {
    276   DCHECK(osdd_url.is_valid());
    277   scoped_ptr<TemplateURLFetcherCallbacks> owned_callbacks(callbacks);
    278 
    279   // For JS added OSDD empty keyword is OK because we will generate keyword
    280   // later from OSDD content.
    281   if (provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER &&
    282       keyword.empty())
    283     return;
    284   TemplateURLModel* url_model = profile()->GetTemplateURLModel();
    285   if (!url_model)
    286     return;
    287 
    288   // Avoid certain checks for the default provider because we'll do the load
    289   // and try to brute force a unique keyword for it.
    290   if (provider_type != TemplateURLFetcher::EXPLICIT_DEFAULT_PROVIDER) {
    291     if (!url_model->loaded()) {
    292       url_model->Load();
    293       return;
    294     }
    295     const TemplateURL* template_url =
    296         url_model->GetTemplateURLForKeyword(keyword);
    297     if (template_url && (!template_url->safe_for_autoreplace() ||
    298                          template_url->originating_url() == osdd_url)) {
    299       // Either there is a user created TemplateURL for this keyword, or the
    300       // keyword has the same OSDD url and we've parsed it.
    301       return;
    302     }
    303   }
    304 
    305   // Make sure we aren't already downloading this request.
    306   for (std::vector<RequestDelegate*>::iterator i = requests_->begin();
    307        i != requests_->end(); ++i) {
    308     bool keyword_or_osdd_match = (*i)->url() == osdd_url ||
    309         (*i)->keyword() == keyword;
    310     bool same_type_or_neither_is_default =
    311         (*i)->provider_type() == provider_type ||
    312         ((*i)->provider_type() != EXPLICIT_DEFAULT_PROVIDER &&
    313          provider_type != EXPLICIT_DEFAULT_PROVIDER);
    314     if (keyword_or_osdd_match && same_type_or_neither_is_default)
    315       return;
    316   }
    317 
    318   requests_->push_back(
    319       new RequestDelegate(this, keyword, osdd_url, favicon_url,
    320                           owned_callbacks.release(), provider_type));
    321 }
    322 
    323 void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) {
    324   DCHECK(find(requests_->begin(), requests_->end(), request) !=
    325          requests_->end());
    326   requests_->erase(find(requests_->begin(), requests_->end(), request));
    327   delete request;
    328 }
    329