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