• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ------------------------------------------------------------
24 class 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.
url() const51   GURL url() const { return osdd_url_; }
52 
53   // Keyword to use.
keyword() const54   string16 keyword() const { return keyword_; }
55 
56   // The type of search provider being fetched.
provider_type() const57   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 
RequestDelegate(TemplateURLFetcher * fetcher,const string16 & keyword,const GURL & osdd_url,const GURL & favicon_url,TemplateURLFetcherCallbacks * callbacks,ProviderType provider_type)77 TemplateURLFetcher::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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)107 void 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 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)119 void 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 
AddSearchProvider()155 void 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 
TemplateURLFetcher(Profile * profile)263 TemplateURLFetcher::TemplateURLFetcher(Profile* profile) : profile_(profile) {
264   DCHECK(profile_);
265 }
266 
~TemplateURLFetcher()267 TemplateURLFetcher::~TemplateURLFetcher() {
268 }
269 
ScheduleDownload(const string16 & keyword,const GURL & osdd_url,const GURL & favicon_url,TemplateURLFetcherCallbacks * callbacks,ProviderType provider_type)270 void 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 
RequestCompleted(RequestDelegate * request)323 void 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