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