普通文本  |  329行  |  12.11 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "build/build_config.h"

#include "chrome/browser/search_engines/template_url_fetcher.h"

#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_fetcher_callbacks.h"
#include "chrome/browser/search_engines/template_url_model.h"
#include "chrome/browser/search_engines/template_url_parser.h"
#include "chrome/common/net/url_fetcher.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "net/url_request/url_request_status.h"

// RequestDelegate ------------------------------------------------------------
class TemplateURLFetcher::RequestDelegate : public URLFetcher::Delegate,
                                            public NotificationObserver {
 public:
  // Takes ownership of |callbacks|.
  RequestDelegate(TemplateURLFetcher* fetcher,
                  const string16& keyword,
                  const GURL& osdd_url,
                  const GURL& favicon_url,
                  TemplateURLFetcherCallbacks* callbacks,
                  ProviderType provider_type);

  // NotificationObserver:
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);

  // URLFetcher::Delegate:
  // If data contains a valid OSDD, a TemplateURL is created and added to
  // the TemplateURLModel.
  virtual void OnURLFetchComplete(const URLFetcher* source,
                                  const GURL& url,
                                  const net::URLRequestStatus& status,
                                  int response_code,
                                  const ResponseCookies& cookies,
                                  const std::string& data);

  // URL of the OSDD.
  GURL url() const { return osdd_url_; }

  // Keyword to use.
  string16 keyword() const { return keyword_; }

  // The type of search provider being fetched.
  ProviderType provider_type() const { return provider_type_; }

 private:
  void AddSearchProvider();

  URLFetcher url_fetcher_;
  TemplateURLFetcher* fetcher_;
  scoped_ptr<TemplateURL> template_url_;
  string16 keyword_;
  const GURL osdd_url_;
  const GURL favicon_url_;
  const ProviderType provider_type_;
  scoped_ptr<TemplateURLFetcherCallbacks> callbacks_;

  // Handles registering for our notifications.
  NotificationRegistrar registrar_;

  DISALLOW_COPY_AND_ASSIGN(RequestDelegate);
};

TemplateURLFetcher::RequestDelegate::RequestDelegate(
    TemplateURLFetcher* fetcher,
    const string16& keyword,
    const GURL& osdd_url,
    const GURL& favicon_url,
    TemplateURLFetcherCallbacks* callbacks,
    ProviderType provider_type)
    : ALLOW_THIS_IN_INITIALIZER_LIST(url_fetcher_(osdd_url,
                                                  URLFetcher::GET, this)),
      fetcher_(fetcher),
      keyword_(keyword),
      osdd_url_(osdd_url),
      favicon_url_(favicon_url),
      provider_type_(provider_type),
      callbacks_(callbacks) {
  TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
  DCHECK(model);  // TemplateURLFetcher::ScheduleDownload verifies this.

  if (!model->loaded()) {
    // Start the model load and set-up waiting for it.
    registrar_.Add(this,
                   NotificationType::TEMPLATE_URL_MODEL_LOADED,
                   Source<TemplateURLModel>(model));
    model->Load();
  }

  url_fetcher_.set_request_context(fetcher->profile()->GetRequestContext());
  url_fetcher_.Start();
}

void TemplateURLFetcher::RequestDelegate::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED);

  if (!template_url_.get())
    return;
  AddSearchProvider();
  // WARNING: AddSearchProvider deletes us.
}

void TemplateURLFetcher::RequestDelegate::OnURLFetchComplete(
    const URLFetcher* source,
    const GURL& url,
    const net::URLRequestStatus& status,
    int response_code,
    const ResponseCookies& cookies,
    const std::string& data) {
  template_url_.reset(new TemplateURL());

  // Validation checks.
  // Make sure we can still replace the keyword, i.e. the fetch was successful.
  // If the OSDD file was loaded HTTP, we also have to check the response_code.
  // For other schemes, e.g. when the OSDD file is bundled with an extension,
  // the response_code is not applicable and should be -1. Also, ensure that
  // the returned information results in a valid search URL.
  if (!status.is_success() ||
      ((response_code != -1) && (response_code != 200)) ||
      !TemplateURLParser::Parse(
          reinterpret_cast<const unsigned char*>(data.c_str()),
          data.length(),
          NULL,
          template_url_.get()) ||
      !template_url_->url() || !template_url_->url()->SupportsReplacement()) {
    fetcher_->RequestCompleted(this);
    // WARNING: RequestCompleted deletes us.
    return;
  }

  // Wait for the model to be loaded before adding the provider.
  TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
  if (!model->loaded())
    return;
  AddSearchProvider();
  // WARNING: AddSearchProvider deletes us.
}

void TemplateURLFetcher::RequestDelegate::AddSearchProvider() {
  DCHECK(template_url_.get());
  if (provider_type_ != AUTODETECTED_PROVIDER || keyword_.empty()) {
    // Generate new keyword from URL in OSDD for none autodetected case.
    // Previous keyword was generated from URL where OSDD was placed and
    // it gives wrong result when OSDD is located on third party site that
    // has nothing in common with search engine in OSDD.
    GURL keyword_url(template_url_->url()->url());
    string16 new_keyword = TemplateURLModel::GenerateKeyword(
        keyword_url, false);
    if (!new_keyword.empty())
      keyword_ = new_keyword;
  }
  TemplateURLModel* model = fetcher_->profile()->GetTemplateURLModel();
  const TemplateURL* existing_url;
  if (keyword_.empty() ||
      !model || !model->loaded() ||
      !model->CanReplaceKeyword(keyword_, GURL(template_url_->url()->url()),
                                &existing_url)) {
    if (provider_type_ == AUTODETECTED_PROVIDER || !model || !model->loaded()) {
      fetcher_->RequestCompleted(this);
      // WARNING: RequestCompleted deletes us.
      return;
    }

    existing_url = NULL;

    // Try to generate a keyword automatically when we are setting the default
    // provider. The keyword isn't as important in this case.
    if (provider_type_ == EXPLICIT_DEFAULT_PROVIDER) {
      // The loop numbers are arbitrary and are simply a strong effort.
      string16 new_keyword;
      for (int i = 0; i < 100; ++i) {
        // Concatenate a number at end of the keyword and try that.
        new_keyword = keyword_;
        // Try the keyword alone the first time
        if (i > 0)
          new_keyword.append(base::IntToString16(i));
        if (!model->GetTemplateURLForKeyword(new_keyword) ||
            model->CanReplaceKeyword(new_keyword,
                                     GURL(template_url_->url()->url()),
                                     &existing_url)) {
          break;
        }
        new_keyword.clear();
        existing_url = NULL;
      }

      if (new_keyword.empty()) {
        // A keyword could not be found. This user must have a lot of numerical
        // keywords built up.
        fetcher_->RequestCompleted(this);
        // WARNING: RequestCompleted deletes us.
        return;
      }
      keyword_ = new_keyword;
    } else {
      // If we're coming from JS (neither autodetected nor failure to load the
      // template URL model) and this URL already exists in the model, we bring
      // up the EditKeywordController to edit it.  This is helpful feedback in
      // the case of clicking a button twice, and annoying in the case of a
      // page that calls AddSearchProvider() in JS without a user action.
      keyword_.clear();
    }
  }

  if (existing_url)
    model->Remove(existing_url);

  // The short name is what is shown to the user. We preserve original names
  // since it is better when generated keyword in many cases.
  template_url_->set_keyword(keyword_);
  template_url_->set_originating_url(osdd_url_);

  // The page may have specified a URL to use for favicons, if not, set it.
  if (!template_url_->GetFaviconURL().is_valid())
    template_url_->SetFaviconURL(favicon_url_);

  switch (provider_type_) {
    case AUTODETECTED_PROVIDER:
      // Mark the keyword as replaceable so it can be removed if necessary.
      template_url_->set_safe_for_autoreplace(true);
      model->Add(template_url_.release());
      break;

    case EXPLICIT_PROVIDER:
      // Confirm addition and allow user to edit default choices. It's ironic
      // that only *non*-autodetected additions get confirmed, but the user
      // expects feedback that his action did something.
      // The source TabContents' delegate takes care of adding the URL to the
      // model, which takes ownership, or of deleting it if the add is
      // cancelled.
      callbacks_->ConfirmAddSearchProvider(template_url_.release(),
                                           fetcher_->profile());
      break;

    case EXPLICIT_DEFAULT_PROVIDER:
      callbacks_->ConfirmSetDefaultSearchProvider(template_url_.release(),
                                                  model);
      break;
  }

  fetcher_->RequestCompleted(this);
  // WARNING: RequestCompleted deletes us.
}

// TemplateURLFetcher ---------------------------------------------------------

TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) {
  DCHECK(profile_);
}

TemplateURLFetcher::~TemplateURLFetcher() {
}

void TemplateURLFetcher::ScheduleDownload(
    const string16& keyword,
    const GURL& osdd_url,
    const GURL& favicon_url,
    TemplateURLFetcherCallbacks* callbacks,
    ProviderType provider_type) {
  DCHECK(osdd_url.is_valid());
  scoped_ptr<TemplateURLFetcherCallbacks> owned_callbacks(callbacks);

  // For JS added OSDD empty keyword is OK because we will generate keyword
  // later from OSDD content.
  if (provider_type == TemplateURLFetcher::AUTODETECTED_PROVIDER &&
      keyword.empty())
    return;
  TemplateURLModel* url_model = profile()->GetTemplateURLModel();
  if (!url_model)
    return;

  // Avoid certain checks for the default provider because we'll do the load
  // and try to brute force a unique keyword for it.
  if (provider_type != TemplateURLFetcher::EXPLICIT_DEFAULT_PROVIDER) {
    if (!url_model->loaded()) {
      url_model->Load();
      return;
    }
    const TemplateURL* template_url =
        url_model->GetTemplateURLForKeyword(keyword);
    if (template_url && (!template_url->safe_for_autoreplace() ||
                         template_url->originating_url() == osdd_url)) {
      // Either there is a user created TemplateURL for this keyword, or the
      // keyword has the same OSDD url and we've parsed it.
      return;
    }
  }

  // Make sure we aren't already downloading this request.
  for (std::vector<RequestDelegate*>::iterator i = requests_->begin();
       i != requests_->end(); ++i) {
    bool keyword_or_osdd_match = (*i)->url() == osdd_url ||
        (*i)->keyword() == keyword;
    bool same_type_or_neither_is_default =
        (*i)->provider_type() == provider_type ||
        ((*i)->provider_type() != EXPLICIT_DEFAULT_PROVIDER &&
         provider_type != EXPLICIT_DEFAULT_PROVIDER);
    if (keyword_or_osdd_match && same_type_or_neither_is_default)
      return;
  }

  requests_->push_back(
      new RequestDelegate(this, keyword, osdd_url, favicon_url,
                          owned_callbacks.release(), provider_type));
}

void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) {
  DCHECK(find(requests_->begin(), requests_->end(), request) !=
         requests_->end());
  requests_->erase(find(requests_->begin(), requests_->end(), request));
  delete request;
}