Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / components / omnibox / autocomplete_input.cc
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_input.h"
6
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "components/metrics/proto/omnibox_event.pb.h"
10 #include "components/omnibox/autocomplete_scheme_classifier.h"
11 #include "components/url_fixer/url_fixer.h"
12 #include "net/base/net_util.h"
13 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
14 #include "url/url_canon_ip.h"
15 #include "url/url_util.h"
16
17 namespace {
18
19 // Hardcode constant to avoid any dependencies on content/.
20 const char kViewSourceScheme[] = "view-source";
21
22 void AdjustCursorPositionIfNecessary(size_t num_leading_chars_removed,
23                                      size_t* cursor_position) {
24   if (*cursor_position == base::string16::npos)
25     return;
26   if (num_leading_chars_removed < *cursor_position)
27     *cursor_position -= num_leading_chars_removed;
28   else
29     *cursor_position = 0;
30 }
31
32 }  // namespace
33
34 AutocompleteInput::AutocompleteInput()
35     : cursor_position_(base::string16::npos),
36       current_page_classification_(metrics::OmniboxEventProto::INVALID_SPEC),
37       type_(metrics::OmniboxInputType::INVALID),
38       prevent_inline_autocomplete_(false),
39       prefer_keyword_(false),
40       allow_exact_keyword_match_(true),
41       want_asynchronous_matches_(true) {
42 }
43
44 AutocompleteInput::AutocompleteInput(
45     const base::string16& text,
46     size_t cursor_position,
47     const std::string& desired_tld,
48     const GURL& current_url,
49     metrics::OmniboxEventProto::PageClassification current_page_classification,
50     bool prevent_inline_autocomplete,
51     bool prefer_keyword,
52     bool allow_exact_keyword_match,
53     bool want_asynchronous_matches,
54     const AutocompleteSchemeClassifier& scheme_classifier)
55     : cursor_position_(cursor_position),
56       current_url_(current_url),
57       current_page_classification_(current_page_classification),
58       prevent_inline_autocomplete_(prevent_inline_autocomplete),
59       prefer_keyword_(prefer_keyword),
60       allow_exact_keyword_match_(allow_exact_keyword_match),
61       want_asynchronous_matches_(want_asynchronous_matches) {
62   DCHECK(cursor_position <= text.length() ||
63          cursor_position == base::string16::npos)
64       << "Text: '" << text << "', cp: " << cursor_position;
65   // None of the providers care about leading white space so we always trim it.
66   // Providers that care about trailing white space handle trimming themselves.
67   if ((base::TrimWhitespace(text, base::TRIM_LEADING, &text_) &
68        base::TRIM_LEADING) != 0)
69     AdjustCursorPositionIfNecessary(text.length() - text_.length(),
70                                     &cursor_position_);
71
72   GURL canonicalized_url;
73   type_ = Parse(text_, desired_tld, scheme_classifier, &parts_, &scheme_,
74                 &canonicalized_url);
75
76   if (type_ == metrics::OmniboxInputType::INVALID)
77     return;
78
79   if (((type_ == metrics::OmniboxInputType::UNKNOWN) ||
80        (type_ == metrics::OmniboxInputType::URL)) &&
81       canonicalized_url.is_valid() &&
82       (!canonicalized_url.IsStandard() || canonicalized_url.SchemeIsFile() ||
83        canonicalized_url.SchemeIsFileSystem() ||
84        !canonicalized_url.host().empty()))
85     canonicalized_url_ = canonicalized_url;
86
87   size_t chars_removed = RemoveForcedQueryStringIfNecessary(type_, &text_);
88   AdjustCursorPositionIfNecessary(chars_removed, &cursor_position_);
89   if (chars_removed) {
90     // Remove spaces between opening question mark and first actual character.
91     base::string16 trimmed_text;
92     if ((base::TrimWhitespace(text_, base::TRIM_LEADING, &trimmed_text) &
93          base::TRIM_LEADING) != 0) {
94       AdjustCursorPositionIfNecessary(text_.length() - trimmed_text.length(),
95                                       &cursor_position_);
96       text_ = trimmed_text;
97     }
98   }
99 }
100
101 AutocompleteInput::~AutocompleteInput() {
102 }
103
104 // static
105 size_t AutocompleteInput::RemoveForcedQueryStringIfNecessary(
106     metrics::OmniboxInputType::Type type,
107     base::string16* text) {
108   if ((type != metrics::OmniboxInputType::FORCED_QUERY) || text->empty() ||
109       (*text)[0] != L'?')
110     return 0;
111   // Drop the leading '?'.
112   text->erase(0, 1);
113   return 1;
114 }
115
116 // static
117 std::string AutocompleteInput::TypeToString(
118     metrics::OmniboxInputType::Type type) {
119   switch (type) {
120     case metrics::OmniboxInputType::INVALID:      return "invalid";
121     case metrics::OmniboxInputType::UNKNOWN:      return "unknown";
122     case metrics::OmniboxInputType::DEPRECATED_REQUESTED_URL:
123       return "deprecated-requested-url";
124     case metrics::OmniboxInputType::URL:          return "url";
125     case metrics::OmniboxInputType::QUERY:        return "query";
126     case metrics::OmniboxInputType::FORCED_QUERY: return "forced-query";
127   }
128   return std::string();
129 }
130
131 // static
132 metrics::OmniboxInputType::Type AutocompleteInput::Parse(
133     const base::string16& text,
134     const std::string& desired_tld,
135     const AutocompleteSchemeClassifier& scheme_classifier,
136     url::Parsed* parts,
137     base::string16* scheme,
138     GURL* canonicalized_url) {
139   size_t first_non_white = text.find_first_not_of(base::kWhitespaceUTF16, 0);
140   if (first_non_white == base::string16::npos)
141     return metrics::OmniboxInputType::INVALID;  // All whitespace.
142
143   if (text[first_non_white] == L'?') {
144     // If the first non-whitespace character is a '?', we magically treat this
145     // as a query.
146     return metrics::OmniboxInputType::FORCED_QUERY;
147   }
148
149   // Ask our parsing back-end to help us understand what the user typed.  We
150   // use the URLFixerUpper here because we want to be smart about what we
151   // consider a scheme.  For example, we shouldn't consider www.google.com:80
152   // to have a scheme.
153   url::Parsed local_parts;
154   if (!parts)
155     parts = &local_parts;
156   const base::string16 parsed_scheme(url_fixer::SegmentURL(text, parts));
157   if (scheme)
158     *scheme = parsed_scheme;
159   const std::string parsed_scheme_utf8(base::UTF16ToUTF8(parsed_scheme));
160
161   // If we can't canonicalize the user's input, the rest of the autocomplete
162   // system isn't going to be able to produce a navigable URL match for it.
163   // So we just return QUERY immediately in these cases.
164   GURL placeholder_canonicalized_url;
165   if (!canonicalized_url)
166     canonicalized_url = &placeholder_canonicalized_url;
167   *canonicalized_url =
168       url_fixer::FixupURL(base::UTF16ToUTF8(text), desired_tld);
169   if (!canonicalized_url->is_valid())
170     return metrics::OmniboxInputType::QUERY;
171
172   if (LowerCaseEqualsASCII(parsed_scheme_utf8, url::kFileScheme)) {
173     // A user might or might not type a scheme when entering a file URL.  In
174     // either case, |parsed_scheme_utf8| will tell us that this is a file URL,
175     // but |parts->scheme| might be empty, e.g. if the user typed "C:\foo".
176     return metrics::OmniboxInputType::URL;
177   }
178
179   // If the user typed a scheme, and it's HTTP or HTTPS, we know how to parse it
180   // well enough that we can fall through to the heuristics below.  If it's
181   // something else, we can just determine our action based on what we do with
182   // any input of this scheme.  In theory we could do better with some schemes
183   // (e.g. "ftp" or "view-source") but I'll wait to spend the effort on that
184   // until I run into some cases that really need it.
185   if (parts->scheme.is_nonempty() &&
186       !LowerCaseEqualsASCII(parsed_scheme_utf8, url::kHttpScheme) &&
187       !LowerCaseEqualsASCII(parsed_scheme_utf8, url::kHttpsScheme)) {
188     metrics::OmniboxInputType::Type type =
189         scheme_classifier.GetInputTypeForScheme(parsed_scheme_utf8);
190     if (type != metrics::OmniboxInputType::INVALID)
191       return type;
192
193     // We don't know about this scheme.  It might be that the user typed a
194     // URL of the form "username:password@foo.com".
195     const base::string16 http_scheme_prefix =
196         base::ASCIIToUTF16(std::string(url::kHttpScheme) +
197                            url::kStandardSchemeSeparator);
198     url::Parsed http_parts;
199     base::string16 http_scheme;
200     GURL http_canonicalized_url;
201     metrics::OmniboxInputType::Type http_type =
202         Parse(http_scheme_prefix + text, desired_tld, scheme_classifier,
203               &http_parts, &http_scheme, &http_canonicalized_url);
204     DCHECK_EQ(std::string(url::kHttpScheme),
205               base::UTF16ToUTF8(http_scheme));
206
207     if ((http_type == metrics::OmniboxInputType::URL) &&
208         http_parts.username.is_nonempty() &&
209         http_parts.password.is_nonempty()) {
210       // Manually re-jigger the parsed parts to match |text| (without the
211       // http scheme added).
212       http_parts.scheme.reset();
213       url::Component* components[] = {
214         &http_parts.username,
215         &http_parts.password,
216         &http_parts.host,
217         &http_parts.port,
218         &http_parts.path,
219         &http_parts.query,
220         &http_parts.ref,
221       };
222       for (size_t i = 0; i < arraysize(components); ++i) {
223         url_fixer::OffsetComponent(
224             -static_cast<int>(http_scheme_prefix.length()), components[i]);
225       }
226
227       *parts = http_parts;
228       if (scheme)
229         scheme->clear();
230       *canonicalized_url = http_canonicalized_url;
231
232       return metrics::OmniboxInputType::URL;
233     }
234
235     // We don't know about this scheme and it doesn't look like the user
236     // typed a username and password.  It's likely to be a search operator
237     // like "site:" or "link:".  We classify it as UNKNOWN so the user has
238     // the option of treating it as a URL if we're wrong.
239     // Note that SegmentURL() is smart so we aren't tricked by "c:\foo" or
240     // "www.example.com:81" in this case.
241     return metrics::OmniboxInputType::UNKNOWN;
242   }
243
244   // Either the user didn't type a scheme, in which case we need to distinguish
245   // between an HTTP URL and a query, or the scheme is HTTP or HTTPS, in which
246   // case we should reject invalid formulations.
247
248   // If we have an empty host it can't be a valid HTTP[S] URL.  (This should
249   // only trigger for input that begins with a colon, which GURL will parse as a
250   // valid, non-standard URL; for standard URLs, an empty host would have
251   // resulted in an invalid |canonicalized_url| above.)
252   if (!canonicalized_url->has_host())
253     return metrics::OmniboxInputType::QUERY;
254
255   // Determine the host family.  We get this information by (re-)canonicalizing
256   // the already-canonicalized host rather than using the user's original input,
257   // in case fixup affected the result here (e.g. an input that looks like an
258   // IPv4 address but with a non-empty desired TLD would return IPV4 before
259   // fixup and NEUTRAL afterwards, and we want to treat it as NEUTRAL).
260   url::CanonHostInfo host_info;
261   net::CanonicalizeHost(canonicalized_url->host(), &host_info);
262
263   // Check if the canonicalized host has a known TLD, which we'll want to know
264   // below.
265   const size_t registry_length =
266       net::registry_controlled_domains::GetRegistryLength(
267           canonicalized_url->host(),
268           net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
269           net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
270   DCHECK_NE(std::string::npos, registry_length);
271   const bool has_known_tld = registry_length != 0;
272
273   // See if the hostname is valid.  While IE and GURL allow hostnames to contain
274   // many other characters (perhaps for weird intranet machines), it's extremely
275   // unlikely that a user would be trying to type those in for anything other
276   // than a search query.
277   const base::string16 original_host(
278       text.substr(parts->host.begin, parts->host.len));
279   if ((host_info.family == url::CanonHostInfo::NEUTRAL) &&
280       !net::IsCanonicalizedHostCompliant(canonicalized_url->host())) {
281     // Invalid hostname.  There are several possible cases:
282     // * The user is typing a multi-word query.  If we see a space anywhere in
283     //   the input host we assume this is a search and return QUERY.  (We check
284     //   the input string instead of canonicalized_url->host() in case fixup
285     //   escaped the space.)
286     // * The user is typing some garbage string.  Return QUERY.
287     // * Our checker is too strict and the user is typing a real-world URL
288     //   that's "invalid" but resolves.  To catch these, we return UNKNOWN when
289     //   the user explicitly typed a scheme or when the hostname has a known
290     //   TLD, so we'll still search by default but we'll show the accidental
291     //   search infobar if necessary.
292     //
293     // This means we would block the following kinds of navigation attempts:
294     // * Navigations to a hostname with spaces
295     // * Navigations to a hostname with invalid characters and an unknown TLD
296     // These might be possible in intranets, but we're not going to support them
297     // without concrete evidence that doing so is necessary.
298     return (parts->scheme.is_nonempty() ||
299         (has_known_tld && (original_host.find(' ') == base::string16::npos))) ?
300         metrics::OmniboxInputType::UNKNOWN : metrics::OmniboxInputType::QUERY;
301   }
302
303   // For hostnames that look like IP addresses, distinguish between IPv6
304   // addresses, which are basically guaranteed to be navigations, and IPv4
305   // addresses, which are much fuzzier.
306   if (host_info.family == url::CanonHostInfo::IPV6)
307     return metrics::OmniboxInputType::URL;
308   if (host_info.family == url::CanonHostInfo::IPV4) {
309     // The host may be a real IP address, or something that looks a bit like it
310     // (e.g. "1.2" or "3232235521").  We check whether it was convertible to an
311     // IP with a non-zero first octet; IPs with first octet zero are "source
312     // IPs" and are never navigable as destination addresses.
313     if (host_info.address[0] == 0)
314       return metrics::OmniboxInputType::QUERY;
315
316     // This is theoretically a navigable IP.  We have four cases.  The first
317     // three are:
318     // * If the user typed four distinct components, this is an IP for sure.
319     // * If the user typed two or three components, this is almost certainly a
320     //   query, especially for two components (as in "13.5/7.25"), but we'll
321     //   allow navigation for an explicit scheme or trailing slash below.
322     // * If the user typed one component, this is likely a query, but could be
323     //   a non-dotted-quad version of an IP address.
324     // Unfortunately, since we called CanonicalizeHost() on the
325     // already-canonicalized host, all of these cases will have been changed to
326     // have four components (e.g. 13.2 -> 13.0.0.2), so we have to call
327     // CanonicalizeHost() again, this time on the original input, so that we can
328     // get the correct number of IP components.
329     //
330     // The fourth case is that the user typed something ambiguous like ".1.2"
331     // that fixup converted to an IP address ("1.0.0.2").  In this case the call
332     // to CanonicalizeHost() will return NEUTRAL here.  Since it's not clear
333     // what the user intended, we fall back to our other heuristics.
334     net::CanonicalizeHost(base::UTF16ToUTF8(original_host), &host_info);
335     if ((host_info.family == url::CanonHostInfo::IPV4) &&
336         (host_info.num_ipv4_components == 4))
337       return metrics::OmniboxInputType::URL;
338   }
339
340   // Now that we've ruled out all schemes other than http or https and done a
341   // little more sanity checking, the presence of a scheme means this is likely
342   // a URL.
343   if (parts->scheme.is_nonempty())
344     return metrics::OmniboxInputType::URL;
345
346   // Trailing slashes force the input to be treated as a URL.
347   if (parts->path.is_nonempty()) {
348     base::char16 c = text[parts->path.end() - 1];
349     if ((c == '\\') || (c == '/'))
350       return metrics::OmniboxInputType::URL;
351   }
352
353   // Handle the cases we detected in the IPv4 code above as "almost certainly a
354   // query" now that we know the user hasn't tried to force navigation via a
355   // scheme/trailing slash.
356   if ((host_info.family == url::CanonHostInfo::IPV4) &&
357       (host_info.num_ipv4_components > 1))
358     return metrics::OmniboxInputType::QUERY;
359
360   // If there is more than one recognized non-host component, this is likely to
361   // be a URL, even if the TLD is unknown (in which case this is likely an
362   // intranet URL).
363   if (NumNonHostComponents(*parts) > 1)
364     return metrics::OmniboxInputType::URL;
365
366   // If we reach here with a username, our input looks something like
367   // "user@host".  Unless there is a desired TLD, we think this is more likely
368   // an email address than an HTTP auth attempt, so we search by default.  (When
369   // there _is_ a desired TLD, the user hit ctrl-enter, and we assume that
370   // implies an attempted navigation.)
371   if (canonicalized_url->has_username() && desired_tld.empty())
372     return metrics::OmniboxInputType::UNKNOWN;
373
374   // If the host has a known TLD or a port, it's probably a URL.  Note that we
375   // special-case "localhost" as a known hostname.
376   if (has_known_tld || (canonicalized_url->host() == "localhost") ||
377       canonicalized_url->has_port())
378     return metrics::OmniboxInputType::URL;
379
380   // If the input looks like a word followed by a pound sign and possibly more
381   // characters ("c#" or "c# foo"), this is almost certainly an attempt to
382   // search.  We try to be conservative here by not firing on cases like "c/#"
383   // or "c?#" that might actually indicate some cryptic attempt to access an
384   // intranet host, and by placing this check late enough that other tests
385   // (e.g., for a non-empty TLD or a non-empty scheme) will have already
386   // returned URL.
387   if (!parts->path.is_valid() && !canonicalized_url->has_query() &&
388       canonicalized_url->has_ref())
389     return metrics::OmniboxInputType::QUERY;
390
391   // No scheme, username, port, and no known TLD on the host.
392   // This could be:
393   // * A single word "foo"; possibly an intranet site, but more likely a search.
394   //   This is ideally an UNKNOWN, and we can let the Alternate Nav URL code
395   //   catch our mistakes.
396   // * A URL with a valid TLD we don't know about yet.  If e.g. a registrar adds
397   //   "xxx" as a TLD, then until we add it to our data file, Chrome won't know
398   //   "foo.xxx" is a real URL.  So ideally this is a URL, but we can't really
399   //   distinguish this case from:
400   // * A "URL-like" string that's not really a URL (like
401   //   "browser.tabs.closeButtons" or "java.awt.event.*").  This is ideally a
402   //   QUERY.  Since this is indistinguishable from the case above, and this
403   //   case is much more likely, claim these are UNKNOWN, which should default
404   //   to the right thing and let users correct us on a case-by-case basis.
405   return metrics::OmniboxInputType::UNKNOWN;
406 }
407
408 // static
409 void AutocompleteInput::ParseForEmphasizeComponents(
410     const base::string16& text,
411     const AutocompleteSchemeClassifier& scheme_classifier,
412     url::Component* scheme,
413     url::Component* host) {
414   url::Parsed parts;
415   base::string16 scheme_str;
416   Parse(text, std::string(), scheme_classifier, &parts, &scheme_str, NULL);
417
418   *scheme = parts.scheme;
419   *host = parts.host;
420
421   int after_scheme_and_colon = parts.scheme.end() + 1;
422   // For the view-source scheme, we should emphasize the scheme and host of the
423   // URL qualified by the view-source prefix.
424   if (LowerCaseEqualsASCII(scheme_str, kViewSourceScheme) &&
425       (static_cast<int>(text.length()) > after_scheme_and_colon)) {
426     // Obtain the URL prefixed by view-source and parse it.
427     base::string16 real_url(text.substr(after_scheme_and_colon));
428     url::Parsed real_parts;
429     AutocompleteInput::Parse(real_url, std::string(), scheme_classifier,
430                              &real_parts, NULL, NULL);
431     if (real_parts.scheme.is_nonempty() || real_parts.host.is_nonempty()) {
432       if (real_parts.scheme.is_nonempty()) {
433         *scheme = url::Component(
434             after_scheme_and_colon + real_parts.scheme.begin,
435             real_parts.scheme.len);
436       } else {
437         scheme->reset();
438       }
439       if (real_parts.host.is_nonempty()) {
440         *host = url::Component(after_scheme_and_colon + real_parts.host.begin,
441                                real_parts.host.len);
442       } else {
443         host->reset();
444       }
445     }
446   } else if (LowerCaseEqualsASCII(scheme_str, url::kFileSystemScheme) &&
447              parts.inner_parsed() && parts.inner_parsed()->scheme.is_valid()) {
448     *host = parts.inner_parsed()->host;
449   }
450 }
451
452 // static
453 base::string16 AutocompleteInput::FormattedStringWithEquivalentMeaning(
454     const GURL& url,
455     const base::string16& formatted_url,
456     const AutocompleteSchemeClassifier& scheme_classifier) {
457   if (!net::CanStripTrailingSlash(url))
458     return formatted_url;
459   const base::string16 url_with_path(formatted_url + base::char16('/'));
460   return (AutocompleteInput::Parse(formatted_url, std::string(),
461                                    scheme_classifier, NULL, NULL, NULL) ==
462           AutocompleteInput::Parse(url_with_path, std::string(),
463                                    scheme_classifier, NULL, NULL, NULL)) ?
464       formatted_url : url_with_path;
465 }
466
467 // static
468 int AutocompleteInput::NumNonHostComponents(const url::Parsed& parts) {
469   int num_nonhost_components = 0;
470   if (parts.scheme.is_nonempty())
471     ++num_nonhost_components;
472   if (parts.username.is_nonempty())
473     ++num_nonhost_components;
474   if (parts.password.is_nonempty())
475     ++num_nonhost_components;
476   if (parts.port.is_nonempty())
477     ++num_nonhost_components;
478   if (parts.path.is_nonempty())
479     ++num_nonhost_components;
480   if (parts.query.is_nonempty())
481     ++num_nonhost_components;
482   if (parts.ref.is_nonempty())
483     ++num_nonhost_components;
484   return num_nonhost_components;
485 }
486
487 // static
488 bool AutocompleteInput::HasHTTPScheme(const base::string16& input) {
489   std::string utf8_input(base::UTF16ToUTF8(input));
490   url::Component scheme;
491   if (url::FindAndCompareScheme(utf8_input, kViewSourceScheme, &scheme)) {
492     utf8_input.erase(0, scheme.end() + 1);
493   }
494   return url::FindAndCompareScheme(utf8_input, url::kHttpScheme, NULL);
495 }
496
497 void AutocompleteInput::UpdateText(const base::string16& text,
498                                    size_t cursor_position,
499                                    const url::Parsed& parts) {
500   DCHECK(cursor_position <= text.length() ||
501          cursor_position == base::string16::npos)
502       << "Text: '" << text << "', cp: " << cursor_position;
503   text_ = text;
504   cursor_position_ = cursor_position;
505   parts_ = parts;
506 }
507
508 void AutocompleteInput::Clear() {
509   text_.clear();
510   cursor_position_ = base::string16::npos;
511   current_url_ = GURL();
512   current_page_classification_ = metrics::OmniboxEventProto::INVALID_SPEC;
513   type_ = metrics::OmniboxInputType::INVALID;
514   parts_ = url::Parsed();
515   scheme_.clear();
516   canonicalized_url_ = GURL();
517   prevent_inline_autocomplete_ = false;
518   prefer_keyword_ = false;
519   allow_exact_keyword_match_ = false;
520   want_asynchronous_matches_ = true;
521 }