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