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