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 "components/omnibox/autocomplete_provider.h"
6
7#include "base/logging.h"
8#include "base/strings/utf_string_conversions.h"
9#include "components/omnibox/autocomplete_input.h"
10#include "components/omnibox/autocomplete_match.h"
11#include "components/url_fixer/url_fixer.h"
12#include "net/base/net_util.h"
13#include "url/gurl.h"
14
15// static
16const size_t AutocompleteProvider::kMaxMatches = 3;
17
18AutocompleteProvider::AutocompleteProvider(Type type)
19    : done_(true),
20      type_(type) {
21}
22
23// static
24const char* AutocompleteProvider::TypeToString(Type type) {
25  switch (type) {
26    case TYPE_BOOKMARK:
27      return "Bookmark";
28    case TYPE_BUILTIN:
29      return "Builtin";
30    case TYPE_HISTORY_QUICK:
31      return "HistoryQuick";
32    case TYPE_HISTORY_URL:
33      return "HistoryURL";
34    case TYPE_KEYWORD:
35      return "Keyword";
36    case TYPE_SEARCH:
37      return "Search";
38    case TYPE_SHORTCUTS:
39      return "Shortcuts";
40    case TYPE_ZERO_SUGGEST:
41      return "ZeroSuggest";
42    default:
43      NOTREACHED() << "Unhandled AutocompleteProvider::Type " << type;
44      return "Unknown";
45  }
46}
47
48void AutocompleteProvider::Stop(bool clear_cached_results) {
49  done_ = true;
50}
51
52const char* AutocompleteProvider::GetName() const {
53  return TypeToString(type_);
54}
55
56metrics::OmniboxEventProto_ProviderType AutocompleteProvider::
57    AsOmniboxEventProviderType() const {
58  switch (type_) {
59    case TYPE_BOOKMARK:
60      return metrics::OmniboxEventProto::BOOKMARK;
61    case TYPE_BUILTIN:
62      return metrics::OmniboxEventProto::BUILTIN;
63    case TYPE_HISTORY_QUICK:
64      return metrics::OmniboxEventProto::HISTORY_QUICK;
65    case TYPE_HISTORY_URL:
66      return metrics::OmniboxEventProto::HISTORY_URL;
67    case TYPE_KEYWORD:
68      return metrics::OmniboxEventProto::KEYWORD;
69    case TYPE_SEARCH:
70      return metrics::OmniboxEventProto::SEARCH;
71    case TYPE_SHORTCUTS:
72      return metrics::OmniboxEventProto::SHORTCUTS;
73    case TYPE_ZERO_SUGGEST:
74      return metrics::OmniboxEventProto::ZERO_SUGGEST;
75    default:
76      NOTREACHED() << "Unhandled AutocompleteProvider::Type " << type_;
77      return metrics::OmniboxEventProto::UNKNOWN_PROVIDER;
78  }
79}
80
81void AutocompleteProvider::DeleteMatch(const AutocompleteMatch& match) {
82  DLOG(WARNING) << "The AutocompleteProvider '" << GetName()
83                << "' has not implemented DeleteMatch.";
84}
85
86void AutocompleteProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
87}
88
89void AutocompleteProvider::ResetSession() {
90}
91
92AutocompleteProvider::~AutocompleteProvider() {
93  Stop(false);
94}
95
96// static
97AutocompleteProvider::FixupReturn AutocompleteProvider::FixupUserInput(
98    const AutocompleteInput& input) {
99  const base::string16& input_text = input.text();
100  const FixupReturn failed(false, input_text);
101
102  // Fixup and canonicalize user input.
103  const GURL canonical_gurl(
104      url_fixer::FixupURL(base::UTF16ToUTF8(input_text), std::string()));
105  std::string canonical_gurl_str(canonical_gurl.possibly_invalid_spec());
106  if (canonical_gurl_str.empty()) {
107    // This probably won't happen, but there are no guarantees.
108    return failed;
109  }
110
111  // If the user types a number, GURL will convert it to a dotted quad.
112  // However, if the parser did not mark this as a URL, then the user probably
113  // didn't intend this interpretation.  Since this can break history matching
114  // for hostname beginning with numbers (e.g. input of "17173" will be matched
115  // against "0.0.67.21" instead of the original "17173", failing to find
116  // "17173.com"), swap the original hostname in for the fixed-up one.
117  if ((input.type() != metrics::OmniboxInputType::URL) &&
118      canonical_gurl.HostIsIPAddress()) {
119    std::string original_hostname =
120        base::UTF16ToUTF8(input_text.substr(input.parts().host.begin,
121                                            input.parts().host.len));
122    const url::Parsed& parts =
123        canonical_gurl.parsed_for_possibly_invalid_spec();
124    // parts.host must not be empty when HostIsIPAddress() is true.
125    DCHECK(parts.host.is_nonempty());
126    canonical_gurl_str.replace(parts.host.begin, parts.host.len,
127                               original_hostname);
128  }
129  base::string16 output(base::UTF8ToUTF16(canonical_gurl_str));
130  // Don't prepend a scheme when the user didn't have one.  Since the fixer
131  // upper only prepends the "http" scheme, that's all we need to check for.
132  if (!AutocompleteInput::HasHTTPScheme(input_text))
133    TrimHttpPrefix(&output);
134
135  // Make the number of trailing slashes on the output exactly match the input.
136  // Examples of why not doing this would matter:
137  // * The user types "a" and has this fixed up to "a/".  Now no other sites
138  //   beginning with "a" will match.
139  // * The user types "file:" and has this fixed up to "file://".  Now inline
140  //   autocomplete will append too few slashes, resulting in e.g. "file:/b..."
141  //   instead of "file:///b..."
142  // * The user types "http:/" and has this fixed up to "http:".  Now inline
143  //   autocomplete will append too many slashes, resulting in e.g.
144  //   "http:///c..." instead of "http://c...".
145  // NOTE: We do this after calling TrimHttpPrefix() since that can strip
146  // trailing slashes (if the scheme is the only thing in the input).  It's not
147  // clear that the result of fixup really matters in this case, but there's no
148  // harm in making sure.
149  const size_t last_input_nonslash =
150      input_text.find_last_not_of(base::ASCIIToUTF16("/\\"));
151  const size_t num_input_slashes =
152      (last_input_nonslash == base::string16::npos) ?
153      input_text.length() : (input_text.length() - 1 - last_input_nonslash);
154  const size_t last_output_nonslash =
155      output.find_last_not_of(base::ASCIIToUTF16("/\\"));
156  const size_t num_output_slashes =
157      (last_output_nonslash == base::string16::npos) ?
158      output.length() : (output.length() - 1 - last_output_nonslash);
159  if (num_output_slashes < num_input_slashes)
160    output.append(num_input_slashes - num_output_slashes, '/');
161  else if (num_output_slashes > num_input_slashes)
162    output.erase(output.length() - num_output_slashes + num_input_slashes);
163  if (output.empty())
164    return failed;
165
166  return FixupReturn(true, output);
167}
168
169// static
170size_t AutocompleteProvider::TrimHttpPrefix(base::string16* url) {
171  // Find any "http:".
172  if (!AutocompleteInput::HasHTTPScheme(*url))
173    return 0;
174  size_t scheme_pos =
175      url->find(base::ASCIIToUTF16(url::kHttpScheme) + base::char16(':'));
176  DCHECK_NE(base::string16::npos, scheme_pos);
177
178  // Erase scheme plus up to two slashes.
179  size_t prefix_end = scheme_pos + strlen(url::kHttpScheme) + 1;
180  const size_t after_slashes = std::min(url->length(), prefix_end + 2);
181  while ((prefix_end < after_slashes) && ((*url)[prefix_end] == '/'))
182    ++prefix_end;
183  url->erase(scheme_pos, prefix_end - scheme_pos);
184  return (scheme_pos == 0) ? prefix_end : 0;
185}
186