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