// 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;
}