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/history_provider.h"
      6 
      7 #include <string>
      8 
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/autocomplete/autocomplete_input.h"
     12 #include "chrome/browser/autocomplete/autocomplete_match.h"
     13 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     14 #include "chrome/browser/history/history_service.h"
     15 #include "chrome/browser/history/history_service_factory.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/common/net/url_fixer_upper.h"
     18 #include "chrome/common/url_constants.h"
     19 #include "url/url_util.h"
     20 
     21 HistoryProvider::HistoryProvider(AutocompleteProviderListener* listener,
     22                                  Profile* profile,
     23                                  AutocompleteProvider::Type type)
     24     : AutocompleteProvider(listener, profile, type) {
     25 }
     26 
     27 void HistoryProvider::DeleteMatch(const AutocompleteMatch& match) {
     28   DCHECK(done_);
     29   DCHECK(profile_);
     30   DCHECK(match.deletable);
     31 
     32   HistoryService* const history_service =
     33       HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
     34 
     35   // Delete the match from the history DB.
     36   DCHECK(history_service);
     37   DCHECK(match.destination_url.is_valid());
     38   history_service->DeleteURL(match.destination_url);
     39   DeleteMatchFromMatches(match);
     40 }
     41 
     42 HistoryProvider::~HistoryProvider() {}
     43 
     44 void HistoryProvider::DeleteMatchFromMatches(const AutocompleteMatch& match) {
     45   bool found = false;
     46   for (ACMatches::iterator i(matches_.begin()); i != matches_.end(); ++i) {
     47     if (i->destination_url == match.destination_url && i->type == match.type) {
     48       found = true;
     49       if (i->is_history_what_you_typed_match || i->starred) {
     50         // We can't get rid of What-You-Typed or Bookmarked matches,
     51         // but we can make them look like they have no backing data.
     52         i->deletable = false;
     53         i->description.clear();
     54         i->description_class.clear();
     55       } else {
     56         matches_.erase(i);
     57       }
     58       break;
     59     }
     60   }
     61   DCHECK(found) << "Asked to delete a URL that isn't in our set of matches";
     62   listener_->OnProviderUpdate(true);
     63 }
     64 
     65 // static
     66 bool HistoryProvider::FixupUserInput(AutocompleteInput* input) {
     67   const string16& input_text = input->text();
     68   // Fixup and canonicalize user input.
     69   const GURL canonical_gurl(URLFixerUpper::FixupURL(UTF16ToUTF8(input_text),
     70                                                     std::string()));
     71   std::string canonical_gurl_str(canonical_gurl.possibly_invalid_spec());
     72   if (canonical_gurl_str.empty()) {
     73     // This probably won't happen, but there are no guarantees.
     74     return false;
     75   }
     76 
     77   // If the user types a number, GURL will convert it to a dotted quad.
     78   // However, if the parser did not mark this as a URL, then the user probably
     79   // didn't intend this interpretation.  Since this can break history matching
     80   // for hostname beginning with numbers (e.g. input of "17173" will be matched
     81   // against "0.0.67.21" instead of the original "17173", failing to find
     82   // "17173.com"), swap the original hostname in for the fixed-up one.
     83   if ((input->type() != AutocompleteInput::URL) &&
     84       canonical_gurl.HostIsIPAddress()) {
     85     std::string original_hostname =
     86         UTF16ToUTF8(input_text.substr(input->parts().host.begin,
     87                                       input->parts().host.len));
     88     const url_parse::Parsed& parts =
     89         canonical_gurl.parsed_for_possibly_invalid_spec();
     90     // parts.host must not be empty when HostIsIPAddress() is true.
     91     DCHECK(parts.host.is_nonempty());
     92     canonical_gurl_str.replace(parts.host.begin, parts.host.len,
     93                                original_hostname);
     94   }
     95   string16 output = UTF8ToUTF16(canonical_gurl_str);
     96   // Don't prepend a scheme when the user didn't have one.  Since the fixer
     97   // upper only prepends the "http" scheme, that's all we need to check for.
     98   if (canonical_gurl.SchemeIs(chrome::kHttpScheme) &&
     99       !url_util::FindAndCompareScheme(UTF16ToUTF8(input_text),
    100                                       chrome::kHttpScheme, NULL))
    101     TrimHttpPrefix(&output);
    102 
    103   // Make the number of trailing slashes on the output exactly match the input.
    104   // Examples of why not doing this would matter:
    105   // * The user types "a" and has this fixed up to "a/".  Now no other sites
    106   //   beginning with "a" will match.
    107   // * The user types "file:" and has this fixed up to "file://".  Now inline
    108   //   autocomplete will append too few slashes, resulting in e.g. "file:/b..."
    109   //   instead of "file:///b..."
    110   // * The user types "http:/" and has this fixed up to "http:".  Now inline
    111   //   autocomplete will append too many slashes, resulting in e.g.
    112   //   "http:///c..." instead of "http://c...".
    113   // NOTE: We do this after calling TrimHttpPrefix() since that can strip
    114   // trailing slashes (if the scheme is the only thing in the input).  It's not
    115   // clear that the result of fixup really matters in this case, but there's no
    116   // harm in making sure.
    117   const size_t last_input_nonslash =
    118       input_text.find_last_not_of(ASCIIToUTF16("/\\"));
    119   const size_t num_input_slashes = (last_input_nonslash == string16::npos) ?
    120       input_text.length() : (input_text.length() - 1 - last_input_nonslash);
    121   const size_t last_output_nonslash =
    122       output.find_last_not_of(ASCIIToUTF16("/\\"));
    123   const size_t num_output_slashes =
    124       (last_output_nonslash == string16::npos) ?
    125       output.length() : (output.length() - 1 - last_output_nonslash);
    126   if (num_output_slashes < num_input_slashes)
    127     output.append(num_input_slashes - num_output_slashes, '/');
    128   else if (num_output_slashes > num_input_slashes)
    129     output.erase(output.length() - num_output_slashes + num_input_slashes);
    130 
    131   url_parse::Parsed parts;
    132   URLFixerUpper::SegmentURL(output, &parts);
    133   input->UpdateText(output, string16::npos, parts);
    134   return !output.empty();
    135 }
    136 
    137 // static
    138 size_t HistoryProvider::TrimHttpPrefix(string16* url) {
    139   // Find any "http:".
    140   if (!HasHTTPScheme(*url))
    141     return 0;
    142   size_t scheme_pos =
    143       url->find(ASCIIToUTF16(chrome::kHttpScheme) + char16(':'));
    144   DCHECK_NE(string16::npos, scheme_pos);
    145 
    146   // Erase scheme plus up to two slashes.
    147   size_t prefix_end = scheme_pos + strlen(chrome::kHttpScheme) + 1;
    148   const size_t after_slashes = std::min(url->length(), prefix_end + 2);
    149   while ((prefix_end < after_slashes) && ((*url)[prefix_end] == '/'))
    150     ++prefix_end;
    151   url->erase(scheme_pos, prefix_end - scheme_pos);
    152   return (scheme_pos == 0) ? prefix_end : 0;
    153 }
    154 
    155 // static
    156 bool HistoryProvider::PreventInlineAutocomplete(
    157     const AutocompleteInput& input) {
    158   return input.prevent_inline_autocomplete() ||
    159       (!input.text().empty() &&
    160        IsWhitespace(input.text()[input.text().length() - 1]));
    161 }
    162