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 "chrome/browser/autocomplete/autocomplete_input.h"
6
7#include "base/strings/string_util.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/external_protocol/external_protocol_handler.h"
10#include "chrome/browser/profiles/profile_io_data.h"
11#include "chrome/common/net/url_fixer_upper.h"
12#include "content/public/common/url_constants.h"
13#include "net/base/net_util.h"
14#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
15#include "url/url_canon_ip.h"
16
17namespace {
18
19void AdjustCursorPositionIfNecessary(size_t num_leading_chars_removed,
20                                     size_t* cursor_position) {
21  if (*cursor_position == string16::npos)
22    return;
23  if (num_leading_chars_removed < *cursor_position)
24    *cursor_position -= num_leading_chars_removed;
25  else
26    *cursor_position = 0;
27}
28
29}  // namespace
30
31AutocompleteInput::AutocompleteInput()
32    : cursor_position_(string16::npos),
33      current_page_classification_(AutocompleteInput::INVALID_SPEC),
34      type_(INVALID),
35      prevent_inline_autocomplete_(false),
36      prefer_keyword_(false),
37      allow_exact_keyword_match_(true),
38      matches_requested_(ALL_MATCHES) {
39}
40
41AutocompleteInput::AutocompleteInput(
42    const string16& text,
43    size_t cursor_position,
44    const string16& desired_tld,
45    const GURL& current_url,
46    AutocompleteInput::PageClassification current_page_classification,
47    bool prevent_inline_autocomplete,
48    bool prefer_keyword,
49    bool allow_exact_keyword_match,
50    MatchesRequested matches_requested)
51    : cursor_position_(cursor_position),
52      current_url_(current_url),
53      current_page_classification_(current_page_classification),
54      prevent_inline_autocomplete_(prevent_inline_autocomplete),
55      prefer_keyword_(prefer_keyword),
56      allow_exact_keyword_match_(allow_exact_keyword_match),
57      matches_requested_(matches_requested) {
58  DCHECK(cursor_position <= text.length() || cursor_position == string16::npos)
59      << "Text: '" << text << "', cp: " << cursor_position;
60  // None of the providers care about leading white space so we always trim it.
61  // Providers that care about trailing white space handle trimming themselves.
62  if ((TrimWhitespace(text, TRIM_LEADING, &text_) & TRIM_LEADING) != 0)
63    AdjustCursorPositionIfNecessary(text.length() - text_.length(),
64                                    &cursor_position_);
65
66  GURL canonicalized_url;
67  type_ = Parse(text_, desired_tld, &parts_, &scheme_, &canonicalized_url);
68
69  if (type_ == INVALID)
70    return;
71
72  if (((type_ == UNKNOWN) || (type_ == URL)) &&
73      canonicalized_url.is_valid() &&
74      (!canonicalized_url.IsStandard() || canonicalized_url.SchemeIsFile() ||
75       canonicalized_url.SchemeIsFileSystem() ||
76       !canonicalized_url.host().empty()))
77    canonicalized_url_ = canonicalized_url;
78
79  size_t chars_removed = RemoveForcedQueryStringIfNecessary(type_, &text_);
80  AdjustCursorPositionIfNecessary(chars_removed, &cursor_position_);
81  if (chars_removed) {
82    // Remove spaces between opening question mark and first actual character.
83    string16 trimmed_text;
84    if ((TrimWhitespace(text_, TRIM_LEADING, &trimmed_text) & TRIM_LEADING) !=
85        0) {
86      AdjustCursorPositionIfNecessary(text_.length() - trimmed_text.length(),
87                                      &cursor_position_);
88      text_ = trimmed_text;
89    }
90  }
91}
92
93AutocompleteInput::~AutocompleteInput() {
94}
95
96// static
97size_t AutocompleteInput::RemoveForcedQueryStringIfNecessary(Type type,
98                                                             string16* text) {
99  if (type != FORCED_QUERY || text->empty() || (*text)[0] != L'?')
100    return 0;
101  // Drop the leading '?'.
102  text->erase(0, 1);
103  return 1;
104}
105
106// static
107std::string AutocompleteInput::TypeToString(Type type) {
108  switch (type) {
109    case INVALID:       return "invalid";
110    case UNKNOWN:       return "unknown";
111    case URL:           return "url";
112    case QUERY:         return "query";
113    case FORCED_QUERY:  return "forced-query";
114
115    default:
116      NOTREACHED();
117      return std::string();
118  }
119}
120
121// static
122AutocompleteInput::Type AutocompleteInput::Parse(
123    const string16& text,
124    const string16& desired_tld,
125    url_parse::Parsed* parts,
126    string16* scheme,
127    GURL* canonicalized_url) {
128  const size_t first_non_white = text.find_first_not_of(kWhitespaceUTF16, 0);
129  if (first_non_white == string16::npos)
130    return INVALID;  // All whitespace.
131
132  if (text.at(first_non_white) == L'?') {
133    // If the first non-whitespace character is a '?', we magically treat this
134    // as a query.
135    return FORCED_QUERY;
136  }
137
138  // Ask our parsing back-end to help us understand what the user typed.  We
139  // use the URLFixerUpper here because we want to be smart about what we
140  // consider a scheme.  For example, we shouldn't consider www.google.com:80
141  // to have a scheme.
142  url_parse::Parsed local_parts;
143  if (!parts)
144    parts = &local_parts;
145  const string16 parsed_scheme(URLFixerUpper::SegmentURL(text, parts));
146  if (scheme)
147    *scheme = parsed_scheme;
148  if (canonicalized_url) {
149    *canonicalized_url = URLFixerUpper::FixupURL(UTF16ToUTF8(text),
150                                                 UTF16ToUTF8(desired_tld));
151  }
152
153  if (LowerCaseEqualsASCII(parsed_scheme, chrome::kFileScheme)) {
154    // A user might or might not type a scheme when entering a file URL.  In
155    // either case, |parsed_scheme| will tell us that this is a file URL, but
156    // |parts->scheme| might be empty, e.g. if the user typed "C:\foo".
157    return URL;
158  }
159
160  if (LowerCaseEqualsASCII(parsed_scheme, chrome::kFileSystemScheme)) {
161    // This could theoretically be a strange search, but let's check.
162    // If it's got an inner_url with a scheme, it's a URL, whether it's valid or
163    // not.
164    if (parts->inner_parsed() && parts->inner_parsed()->scheme.is_valid())
165      return URL;
166  }
167
168  // If the user typed a scheme, and it's HTTP or HTTPS, we know how to parse it
169  // well enough that we can fall through to the heuristics below.  If it's
170  // something else, we can just determine our action based on what we do with
171  // any input of this scheme.  In theory we could do better with some schemes
172  // (e.g. "ftp" or "view-source") but I'll wait to spend the effort on that
173  // until I run into some cases that really need it.
174  if (parts->scheme.is_nonempty() &&
175      !LowerCaseEqualsASCII(parsed_scheme, chrome::kHttpScheme) &&
176      !LowerCaseEqualsASCII(parsed_scheme, chrome::kHttpsScheme)) {
177    // See if we know how to handle the URL internally.
178    if (ProfileIOData::IsHandledProtocol(UTF16ToASCII(parsed_scheme)))
179      return URL;
180
181    // There are also some schemes that we convert to other things before they
182    // reach the renderer or else the renderer handles internally without
183    // reaching the net::URLRequest logic.  We thus won't catch these above, but
184    // we should still claim to handle them.
185    if (LowerCaseEqualsASCII(parsed_scheme, content::kViewSourceScheme) ||
186        LowerCaseEqualsASCII(parsed_scheme, chrome::kJavaScriptScheme) ||
187        LowerCaseEqualsASCII(parsed_scheme, chrome::kDataScheme))
188      return URL;
189
190    // Finally, check and see if the user has explicitly opened this scheme as
191    // a URL before, or if the "scheme" is actually a username.  We need to do
192    // this last because some schemes (e.g. "javascript") may be treated as
193    // "blocked" by the external protocol handler because we don't want pages to
194    // open them, but users still can.
195    // TODO(viettrungluu): get rid of conversion.
196    ExternalProtocolHandler::BlockState block_state =
197        ExternalProtocolHandler::GetBlockState(UTF16ToUTF8(parsed_scheme));
198    switch (block_state) {
199      case ExternalProtocolHandler::DONT_BLOCK:
200        return URL;
201
202      case ExternalProtocolHandler::BLOCK:
203        // If we don't want the user to open the URL, don't let it be navigated
204        // to at all.
205        return QUERY;
206
207      default: {
208        // We don't know about this scheme.  It might be that the user typed a
209        // URL of the form "username:password@foo.com".
210        const string16 http_scheme_prefix =
211            ASCIIToUTF16(std::string(chrome::kHttpScheme) +
212                         content::kStandardSchemeSeparator);
213        url_parse::Parsed http_parts;
214        string16 http_scheme;
215        GURL http_canonicalized_url;
216        Type http_type = Parse(http_scheme_prefix + text, desired_tld,
217                               &http_parts, &http_scheme,
218                               &http_canonicalized_url);
219        DCHECK_EQ(std::string(chrome::kHttpScheme), UTF16ToUTF8(http_scheme));
220
221        if (http_type == URL &&
222            http_parts.username.is_nonempty() &&
223            http_parts.password.is_nonempty()) {
224          // Manually re-jigger the parsed parts to match |text| (without the
225          // http scheme added).
226          http_parts.scheme.reset();
227          url_parse::Component* components[] = {
228            &http_parts.username,
229            &http_parts.password,
230            &http_parts.host,
231            &http_parts.port,
232            &http_parts.path,
233            &http_parts.query,
234            &http_parts.ref,
235          };
236          for (size_t i = 0; i < arraysize(components); ++i) {
237            URLFixerUpper::OffsetComponent(
238                -static_cast<int>(http_scheme_prefix.length()), components[i]);
239          }
240
241          *parts = http_parts;
242          if (scheme)
243            scheme->clear();
244          if (canonicalized_url)
245            *canonicalized_url = http_canonicalized_url;
246
247          return http_type;
248        }
249
250        // We don't know about this scheme and it doesn't look like the user
251        // typed a username and password.  It's likely to be a search operator
252        // like "site:" or "link:".  We classify it as UNKNOWN so the user has
253        // the option of treating it as a URL if we're wrong.
254        // Note that SegmentURL() is smart so we aren't tricked by "c:\foo" or
255        // "www.example.com:81" in this case.
256        return UNKNOWN;
257      }
258    }
259  }
260
261  // Either the user didn't type a scheme, in which case we need to distinguish
262  // between an HTTP URL and a query, or the scheme is HTTP or HTTPS, in which
263  // case we should reject invalid formulations.
264
265  // If we have an empty host it can't be a URL.
266  if (!parts->host.is_nonempty())
267    return QUERY;
268
269  // Likewise, the RCDS can reject certain obviously-invalid hosts.  (We also
270  // use the registry length later below.)
271  const string16 host(text.substr(parts->host.begin, parts->host.len));
272  const size_t registry_length =
273      net::registry_controlled_domains::GetRegistryLength(
274          UTF16ToUTF8(host),
275          net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
276          net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
277  if (registry_length == std::string::npos) {
278    // Try to append the desired_tld.
279    if (!desired_tld.empty()) {
280      string16 host_with_tld(host);
281      if (host[host.length() - 1] != '.')
282        host_with_tld += '.';
283      host_with_tld += desired_tld;
284      const size_t tld_length =
285          net::registry_controlled_domains::GetRegistryLength(
286              UTF16ToUTF8(host_with_tld),
287              net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
288              net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
289      if (tld_length != std::string::npos)
290        return URL;  // Something like "99999999999" that looks like a bad IP
291                     // address, but becomes valid on attaching a TLD.
292    }
293    return QUERY;  // Could be a broken IP address, etc.
294  }
295
296
297  // See if the hostname is valid.  While IE and GURL allow hostnames to contain
298  // many other characters (perhaps for weird intranet machines), it's extremely
299  // unlikely that a user would be trying to type those in for anything other
300  // than a search query.
301  url_canon::CanonHostInfo host_info;
302  const std::string canonicalized_host(net::CanonicalizeHost(UTF16ToUTF8(host),
303                                                             &host_info));
304  if ((host_info.family == url_canon::CanonHostInfo::NEUTRAL) &&
305      !net::IsCanonicalizedHostCompliant(canonicalized_host,
306                                         UTF16ToUTF8(desired_tld))) {
307    // Invalid hostname.  There are several possible cases:
308    // * Our checker is too strict and the user pasted in a real-world URL
309    //   that's "invalid" but resolves.  To catch these, we return UNKNOWN when
310    //   the user explicitly typed a scheme, so we'll still search by default
311    //   but we'll show the accidental search infobar if necessary.
312    // * The user is typing a multi-word query.  If we see a space anywhere in
313    //   the hostname we assume this is a search and return QUERY.
314    // * Our checker is too strict and the user is typing a real-world hostname
315    //   that's "invalid" but resolves.  We return UNKNOWN if the TLD is known.
316    //   Note that we explicitly excluded hosts with spaces above so that
317    //   "toys at amazon.com" will be treated as a search.
318    // * The user is typing some garbage string.  Return QUERY.
319    //
320    // Thus we fall down in the following cases:
321    // * Trying to navigate to a hostname with spaces
322    // * Trying to navigate to a hostname with invalid characters and an unknown
323    //   TLD
324    // These are rare, though probably possible in intranets.
325    return (parts->scheme.is_nonempty() ||
326           ((registry_length != 0) && (host.find(' ') == string16::npos))) ?
327        UNKNOWN : QUERY;
328  }
329
330  // A port number is a good indicator that this is a URL.  However, it might
331  // also be a query like "1.66:1" that looks kind of like an IP address and
332  // port number. So here we only check for "port numbers" that are illegal and
333  // thus mean this can't be navigated to (e.g. "1.2.3.4:garbage"), and we save
334  // handling legal port numbers until after the "IP address" determination
335  // below.
336  if (url_parse::ParsePort(text.c_str(), parts->port) ==
337      url_parse::PORT_INVALID)
338    return QUERY;
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 URL;
345
346  // See if the host is an IP address.
347  if (host_info.family == url_canon::CanonHostInfo::IPV6)
348    return URL;
349  // If the user originally typed a host that looks like an IP address (a
350  // dotted quad), they probably want to open it.  If the original input was
351  // something else (like a single number), they probably wanted to search for
352  // it, unless they explicitly typed a scheme.  This is true even if the URL
353  // appears to have a path: "1.2/45" is more likely a search (for the answer
354  // to a math problem) than a URL.  However, if there are more non-host
355  // components, then maybe this really was intended to be a navigation.  For
356  // this reason we only check the dotted-quad case here, and save the "other
357  // IP addresses" case for after we check the number of non-host components
358  // below.
359  if ((host_info.family == url_canon::CanonHostInfo::IPV4) &&
360      (host_info.num_ipv4_components == 4))
361    return URL;
362
363  // Presence of a password means this is likely a URL.  Note that unless the
364  // user has typed an explicit "http://" or similar, we'll probably think that
365  // the username is some unknown scheme, and bail out in the scheme-handling
366  // code above.
367  if (parts->password.is_nonempty())
368    return URL;
369
370  // Trailing slashes force the input to be treated as a URL.
371  if (parts->path.is_nonempty()) {
372    char c = text[parts->path.end() - 1];
373    if ((c == '\\') || (c == '/'))
374      return URL;
375  }
376
377  // If there is more than one recognized non-host component, this is likely to
378  // be a URL, even if the TLD is unknown (in which case this is likely an
379  // intranet URL).
380  if (NumNonHostComponents(*parts) > 1)
381    return URL;
382
383  // If the host has a known TLD or a port, it's probably a URL, with the
384  // following exceptions:
385  // * Any "IP addresses" that make it here are more likely searches
386  //   (see above).
387  // * If we reach here with a username, our input looks like "user@host[.tld]".
388  //   Because there is no scheme explicitly specified, we think this is more
389  //   likely an email address than an HTTP auth attempt.  Hence, we search by
390  //   default and let users correct us on a case-by-case basis.
391  // Note that we special-case "localhost" as a known hostname.
392  if ((host_info.family != url_canon::CanonHostInfo::IPV4) &&
393      ((registry_length != 0) || (host == ASCIIToUTF16("localhost") ||
394       parts->port.is_nonempty())))
395    return parts->username.is_nonempty() ? UNKNOWN : URL;
396
397  // If we reach this point, we know there's no known TLD on the input, so if
398  // the user wishes to add a desired_tld, the fixup code will oblige; thus this
399  // is a URL.
400  if (!desired_tld.empty())
401    return URL;
402
403  // No scheme, password, port, path, and no known TLD on the host.
404  // This could be:
405  // * An "incomplete IP address"; likely a search (see above).
406  // * An email-like input like "user@host", where "host" has no known TLD.
407  //   It's not clear what the user means here and searching seems reasonable.
408  // * A single word "foo"; possibly an intranet site, but more likely a search.
409  //   This is ideally an UNKNOWN, and we can let the Alternate Nav URL code
410  //   catch our mistakes.
411  // * A URL with a valid TLD we don't know about yet.  If e.g. a registrar adds
412  //   "xxx" as a TLD, then until we add it to our data file, Chrome won't know
413  //   "foo.xxx" is a real URL.  So ideally this is a URL, but we can't really
414  //   distinguish this case from:
415  // * A "URL-like" string that's not really a URL (like
416  //   "browser.tabs.closeButtons" or "java.awt.event.*").  This is ideally a
417  //   QUERY.  Since this is indistinguishable from the case above, and this
418  //   case is much more likely, claim these are UNKNOWN, which should default
419  //   to the right thing and let users correct us on a case-by-case basis.
420  return UNKNOWN;
421}
422
423// static
424void AutocompleteInput::ParseForEmphasizeComponents(
425    const string16& text,
426    url_parse::Component* scheme,
427    url_parse::Component* host) {
428  url_parse::Parsed parts;
429  string16 scheme_str;
430  Parse(text, string16(), &parts, &scheme_str, NULL);
431
432  *scheme = parts.scheme;
433  *host = parts.host;
434
435  int after_scheme_and_colon = parts.scheme.end() + 1;
436  // For the view-source scheme, we should emphasize the scheme and host of the
437  // URL qualified by the view-source prefix.
438  if (LowerCaseEqualsASCII(scheme_str, content::kViewSourceScheme) &&
439      (static_cast<int>(text.length()) > after_scheme_and_colon)) {
440    // Obtain the URL prefixed by view-source and parse it.
441    string16 real_url(text.substr(after_scheme_and_colon));
442    url_parse::Parsed real_parts;
443    AutocompleteInput::Parse(real_url, string16(), &real_parts, NULL, NULL);
444    if (real_parts.scheme.is_nonempty() || real_parts.host.is_nonempty()) {
445      if (real_parts.scheme.is_nonempty()) {
446        *scheme = url_parse::Component(
447            after_scheme_and_colon + real_parts.scheme.begin,
448            real_parts.scheme.len);
449      } else {
450        scheme->reset();
451      }
452      if (real_parts.host.is_nonempty()) {
453        *host = url_parse::Component(
454            after_scheme_and_colon + real_parts.host.begin,
455            real_parts.host.len);
456      } else {
457        host->reset();
458      }
459    }
460  } else if (LowerCaseEqualsASCII(scheme_str, chrome::kFileSystemScheme) &&
461             parts.inner_parsed() && parts.inner_parsed()->scheme.is_valid()) {
462    *host = parts.inner_parsed()->host;
463  }
464}
465
466// static
467string16 AutocompleteInput::FormattedStringWithEquivalentMeaning(
468    const GURL& url,
469    const string16& formatted_url) {
470  if (!net::CanStripTrailingSlash(url))
471    return formatted_url;
472  const string16 url_with_path(formatted_url + char16('/'));
473  return (AutocompleteInput::Parse(formatted_url, string16(), NULL, NULL,
474                                   NULL) ==
475          AutocompleteInput::Parse(url_with_path, string16(), NULL, NULL,
476                                   NULL)) ?
477      formatted_url : url_with_path;
478}
479
480// static
481int AutocompleteInput::NumNonHostComponents(const url_parse::Parsed& parts) {
482  int num_nonhost_components = 0;
483  if (parts.scheme.is_nonempty())
484    ++num_nonhost_components;
485  if (parts.username.is_nonempty())
486    ++num_nonhost_components;
487  if (parts.password.is_nonempty())
488    ++num_nonhost_components;
489  if (parts.port.is_nonempty())
490    ++num_nonhost_components;
491  if (parts.path.is_nonempty())
492    ++num_nonhost_components;
493  if (parts.query.is_nonempty())
494    ++num_nonhost_components;
495  if (parts.ref.is_nonempty())
496    ++num_nonhost_components;
497  return num_nonhost_components;
498}
499
500void AutocompleteInput::UpdateText(const string16& text,
501                                   size_t cursor_position,
502                                   const url_parse::Parsed& parts) {
503  DCHECK(cursor_position <= text.length() || cursor_position == string16::npos)
504      << "Text: '" << text << "', cp: " << cursor_position;
505  text_ = text;
506  cursor_position_ = cursor_position;
507  parts_ = parts;
508}
509
510void AutocompleteInput::Clear() {
511  text_.clear();
512  cursor_position_ = string16::npos;
513  current_url_ = GURL();
514  current_page_classification_ = AutocompleteInput::INVALID_SPEC;
515  type_ = INVALID;
516  parts_ = url_parse::Parsed();
517  scheme_.clear();
518  canonicalized_url_ = GURL();
519  prevent_inline_autocomplete_ = false;
520  prefer_keyword_ = false;
521  allow_exact_keyword_match_ = false;
522  matches_requested_ = ALL_MATCHES;
523}
524