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