Home | History | Annotate | Download | only in autocomplete
      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 
     17 namespace {
     18 
     19 void 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 
     31 AutocompleteInput::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 
     41 AutocompleteInput::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 
     93 AutocompleteInput::~AutocompleteInput() {
     94 }
     95 
     96 // static
     97 size_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
    107 std::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
    122 AutocompleteInput::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 (at) 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
    424 void 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
    467 string16 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
    481 int 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 
    500 void 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 
    510 void 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