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