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