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 "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
6
7#include "base/string_util.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/metrics/user_metrics.h"
10#include "chrome/browser/net/url_fixer_upper.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_model.h"
14#include "googleurl/src/gurl.h"
15
16EditSearchEngineController::EditSearchEngineController(
17    const TemplateURL* template_url,
18    EditSearchEngineControllerDelegate* edit_keyword_delegate,
19    Profile* profile)
20    : template_url_(template_url),
21      edit_keyword_delegate_(edit_keyword_delegate),
22      profile_(profile) {
23  DCHECK(profile_);
24}
25
26bool EditSearchEngineController::IsTitleValid(
27    const string16& title_input) const {
28  return !CollapseWhitespace(title_input, true).empty();
29}
30
31bool EditSearchEngineController::IsURLValid(
32    const std::string& url_input) const {
33  std::string url = GetFixedUpURL(url_input);
34  if (url.empty())
35    return false;
36
37  // Use TemplateURLRef to extract the search placeholder.
38  TemplateURLRef template_ref(url, 0, 0);
39  if (!template_ref.IsValid())
40    return false;
41
42  if (!template_ref.SupportsReplacement()) {
43    // If this is the default search engine, there must be a search term
44    // placeholder.
45    if (template_url_ ==
46        profile_->GetTemplateURLModel()->GetDefaultSearchProvider())
47      return false;
48    return GURL(url).is_valid();
49  }
50
51  // If the url has a search term, replace it with a random string and make
52  // sure the resulting URL is valid. We don't check the validity of the url
53  // with the search term as that is not necessarily valid.
54  return GURL(template_ref.ReplaceSearchTerms(TemplateURL(), ASCIIToUTF16("a"),
55      TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())).is_valid();
56}
57
58bool EditSearchEngineController::IsKeywordValid(
59    const string16& keyword_input) const {
60  string16 keyword_input_trimmed(CollapseWhitespace(keyword_input, true));
61  if (keyword_input_trimmed.empty())
62    return false;  // Do not allow empty keyword.
63  const TemplateURL* turl_with_keyword =
64      profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(
65          keyword_input_trimmed);
66  return (turl_with_keyword == NULL || turl_with_keyword == template_url_);
67}
68
69void EditSearchEngineController::AcceptAddOrEdit(
70    const string16& title_input,
71    const string16& keyword_input,
72    const std::string& url_input) {
73  std::string url_string = GetFixedUpURL(url_input);
74  DCHECK(!url_string.empty());
75
76  const TemplateURL* existing =
77      profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(
78          keyword_input);
79  if (existing &&
80      (!edit_keyword_delegate_ || existing != template_url_)) {
81    // An entry may have been added with the same keyword string while the
82    // user edited the dialog, either automatically or by the user (if we're
83    // confirming a JS addition, they could have the Options dialog open at the
84    // same time). If so, just ignore this add.
85    // TODO(pamg): Really, we should modify the entry so this later one
86    // overwrites it. But we don't expect this case to be common.
87    CleanUpCancelledAdd();
88    return;
89  }
90
91  if (!edit_keyword_delegate_) {
92    // Confiming an entry we got from JS. We have a template_url_, but it
93    // hasn't yet been added to the model.
94    DCHECK(template_url_);
95    // const_cast is ugly, but this is the same thing the TemplateURLModel
96    // does in a similar situation (updating an existing TemplateURL with
97    // data from a new one).
98    TemplateURL* modifiable_url = const_cast<TemplateURL*>(template_url_);
99    modifiable_url->set_short_name(title_input);
100    modifiable_url->set_keyword(keyword_input);
101    modifiable_url->SetURL(url_string, 0, 0);
102    // TemplateURLModel takes ownership of template_url_.
103    profile_->GetTemplateURLModel()->Add(modifiable_url);
104    UserMetrics::RecordAction(UserMetricsAction("KeywordEditor_AddKeywordJS"),
105                              profile_);
106  } else {
107    // Adding or modifying an entry via the Delegate.
108    edit_keyword_delegate_->OnEditedKeyword(template_url_,
109                                            title_input,
110                                            keyword_input,
111                                            url_string);
112  }
113}
114
115void EditSearchEngineController::CleanUpCancelledAdd() {
116  if (!edit_keyword_delegate_ && template_url_) {
117    // When we have no Delegate, we know that the template_url_ hasn't yet been
118    // added to the model, so we need to clean it up.
119    delete template_url_;
120    template_url_ = NULL;
121  }
122}
123
124std::string EditSearchEngineController::GetFixedUpURL(
125    const std::string& url_input) const {
126  std::string url;
127  TrimWhitespace(TemplateURLRef::DisplayURLToURLRef(UTF8ToUTF16(url_input)),
128                 TRIM_ALL, &url);
129  if (url.empty())
130    return url;
131
132  // Parse the string as a URL to determine the scheme. If we need to, add the
133  // scheme. As the scheme may be expanded (as happens with {google:baseURL})
134  // we need to replace the search terms before testing for the scheme.
135  TemplateURL t_url;
136  t_url.SetURL(url, 0, 0);
137  std::string expanded_url =
138      t_url.url()->ReplaceSearchTerms(t_url, ASCIIToUTF16("x"), 0, string16());
139  url_parse::Parsed parts;
140  std::string scheme(
141      URLFixerUpper::SegmentURL(expanded_url, &parts));
142  if (!parts.scheme.is_valid()) {
143    scheme.append("://");
144    url.insert(0, scheme);
145  }
146
147  return url;
148}
149
150