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 ------------------------------------------------------------
24class 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
77TemplateURLFetcher::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
107void 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
119void 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
155void 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
263TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) {
264  DCHECK(profile_);
265}
266
267TemplateURLFetcher::~TemplateURLFetcher() {
268}
269
270void 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
323void 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