| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/autocomplete_history_manager.h" |
| |
| #include <vector> |
| |
| #include "base/string16.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/autofill/credit_card.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/autofill_messages.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "webkit/glue/form_data.h" |
| |
| using webkit_glue::FormData; |
| using webkit_glue::FormField; |
| |
| namespace { |
| |
| // Limit on the number of suggestions to appear in the pop-up menu under an |
| // text input element in a form. |
| const int kMaxAutocompleteMenuItems = 6; |
| |
| // The separator characters for SSNs. |
| const string16 kSSNSeparators = ASCIIToUTF16(" -"); |
| |
| bool IsSSN(const string16& text) { |
| string16 number_string; |
| RemoveChars(text, kSSNSeparators.c_str(), &number_string); |
| |
| // A SSN is of the form AAA-GG-SSSS (A = area number, G = group number, S = |
| // serial number). The validation we do here is simply checking if the area, |
| // group, and serial numbers are valid. |
| // |
| // Historically, the area number was assigned per state, with the group number |
| // ascending in an alternating even/odd sequence. With that scheme it was |
| // possible to check for validity by referencing a table that had the highest |
| // group number assigned for a given area number. (This was something that |
| // Chromium never did though, because the "high group" values were constantly |
| // changing.) |
| // |
| // However, starting on 25 June 2011 the SSA began issuing SSNs randomly from |
| // all areas and groups. Group numbers and serial numbers of zero remain |
| // invalid, and areas 000, 666, and 900-999 remain invalid. |
| // |
| // References for current practices: |
| // http://www.socialsecurity.gov/employer/randomization.html |
| // http://www.socialsecurity.gov/employer/randomizationfaqs.html |
| // |
| // References for historic practices: |
| // http://www.socialsecurity.gov/history/ssn/geocard.html |
| // http://www.socialsecurity.gov/employer/stateweb.htm |
| // http://www.socialsecurity.gov/employer/ssnvhighgroup.htm |
| |
| if (number_string.length() != 9 || !IsStringASCII(number_string)) |
| return false; |
| |
| int area; |
| if (!base::StringToInt(number_string.begin(), |
| number_string.begin() + 3, |
| &area)) |
| return false; |
| if (area < 1 || |
| area == 666 || |
| area >= 900) |
| return false; |
| |
| int group; |
| if (!base::StringToInt(number_string.begin() + 3, |
| number_string.begin() + 5, |
| &group) || group == 0) |
| return false; |
| |
| int serial; |
| if (!base::StringToInt(number_string.begin() + 5, |
| number_string.begin() + 9, |
| &serial) || serial == 0) |
| return false; |
| |
| return true; |
| } |
| |
| bool IsTextField(const FormField& field) { |
| return |
| field.form_control_type == ASCIIToUTF16("text") || |
| field.form_control_type == ASCIIToUTF16("search") || |
| field.form_control_type == ASCIIToUTF16("tel") || |
| field.form_control_type == ASCIIToUTF16("url") || |
| field.form_control_type == ASCIIToUTF16("email") || |
| field.form_control_type == ASCIIToUTF16("text"); |
| } |
| |
| } // namespace |
| |
| AutocompleteHistoryManager::AutocompleteHistoryManager( |
| TabContents* tab_contents) |
| : TabContentsObserver(tab_contents), |
| pending_query_handle_(0), |
| query_id_(0) { |
| profile_ = Profile::FromBrowserContext(tab_contents->browser_context()); |
| // May be NULL in unit tests. |
| web_data_service_ = profile_->GetWebDataService(Profile::EXPLICIT_ACCESS); |
| autofill_enabled_.Init(prefs::kAutofillEnabled, profile_->GetPrefs(), NULL); |
| } |
| |
| AutocompleteHistoryManager::~AutocompleteHistoryManager() { |
| CancelPendingQuery(); |
| } |
| |
| bool AutocompleteHistoryManager::OnMessageReceived( |
| const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(AutocompleteHistoryManager, message) |
| IPC_MESSAGE_HANDLER(AutofillHostMsg_RemoveAutocompleteEntry, |
| OnRemoveAutocompleteEntry) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void AutocompleteHistoryManager::OnFormSubmitted(const FormData& form) { |
| if (!*autofill_enabled_) |
| return; |
| |
| if (profile_->IsOffTheRecord()) |
| return; |
| |
| // Don't save data that was submitted through JavaScript. |
| if (!form.user_submitted) |
| return; |
| |
| // We put the following restriction on stored FormFields: |
| // - non-empty name |
| // - non-empty value |
| // - text field |
| // - value is not a credit card number |
| // - value is not a SSN |
| std::vector<FormField> values; |
| for (std::vector<FormField>::const_iterator iter = |
| form.fields.begin(); |
| iter != form.fields.end(); ++iter) { |
| if (!iter->value.empty() && |
| !iter->name.empty() && |
| IsTextField(*iter) && |
| !CreditCard::IsValidCreditCardNumber(iter->value) && |
| !IsSSN(iter->value)) { |
| values.push_back(*iter); |
| } |
| } |
| |
| if (!values.empty() && web_data_service_.get()) |
| web_data_service_->AddFormFields(values); |
| } |
| |
| void AutocompleteHistoryManager::OnRemoveAutocompleteEntry( |
| const string16& name, const string16& value) { |
| if (web_data_service_.get()) |
| web_data_service_->RemoveFormValueForElementName(name, value); |
| } |
| |
| void AutocompleteHistoryManager::OnGetAutocompleteSuggestions( |
| int query_id, |
| const string16& name, |
| const string16& prefix, |
| const std::vector<string16>& autofill_values, |
| const std::vector<string16>& autofill_labels, |
| const std::vector<string16>& autofill_icons, |
| const std::vector<int>& autofill_unique_ids) { |
| CancelPendingQuery(); |
| |
| query_id_ = query_id; |
| autofill_values_ = autofill_values; |
| autofill_labels_ = autofill_labels; |
| autofill_icons_ = autofill_icons; |
| autofill_unique_ids_ = autofill_unique_ids; |
| if (!*autofill_enabled_) { |
| SendSuggestions(NULL); |
| return; |
| } |
| |
| if (web_data_service_.get()) { |
| pending_query_handle_ = web_data_service_->GetFormValuesForElementName( |
| name, prefix, kMaxAutocompleteMenuItems, this); |
| } |
| } |
| |
| void AutocompleteHistoryManager::OnWebDataServiceRequestDone( |
| WebDataService::Handle h, |
| const WDTypedResult* result) { |
| DCHECK(pending_query_handle_); |
| pending_query_handle_ = 0; |
| |
| if (!*autofill_enabled_) { |
| SendSuggestions(NULL); |
| return; |
| } |
| |
| DCHECK(result); |
| // Returning early here if |result| is NULL. We've seen this happen on |
| // Linux due to NFS dismounting and causing sql failures. |
| // See http://crbug.com/68783. |
| if (!result) { |
| SendSuggestions(NULL); |
| return; |
| } |
| |
| DCHECK_EQ(AUTOFILL_VALUE_RESULT, result->GetType()); |
| const WDResult<std::vector<string16> >* autofill_result = |
| static_cast<const WDResult<std::vector<string16> >*>(result); |
| std::vector<string16> suggestions = autofill_result->GetValue(); |
| SendSuggestions(&suggestions); |
| } |
| |
| AutocompleteHistoryManager::AutocompleteHistoryManager( |
| TabContents* tab_contents, |
| Profile* profile, |
| WebDataService* wds) |
| : TabContentsObserver(tab_contents), |
| profile_(profile), |
| web_data_service_(wds), |
| pending_query_handle_(0), |
| query_id_(0) { |
| autofill_enabled_.Init( |
| prefs::kAutofillEnabled, profile_->GetPrefs(), NULL); |
| } |
| |
| void AutocompleteHistoryManager::CancelPendingQuery() { |
| if (pending_query_handle_) { |
| SendSuggestions(NULL); |
| if (web_data_service_.get()) |
| web_data_service_->CancelRequest(pending_query_handle_); |
| pending_query_handle_ = 0; |
| } |
| } |
| |
| void AutocompleteHistoryManager::SendSuggestions( |
| const std::vector<string16>* suggestions) { |
| if (suggestions) { |
| // Combine Autofill and Autocomplete values into values and labels. |
| for (size_t i = 0; i < suggestions->size(); ++i) { |
| bool unique = true; |
| for (size_t j = 0; j < autofill_values_.size(); ++j) { |
| // Don't add duplicate values. |
| if (autofill_values_[j] == (*suggestions)[i]) { |
| unique = false; |
| break; |
| } |
| } |
| |
| if (unique) { |
| autofill_values_.push_back((*suggestions)[i]); |
| autofill_labels_.push_back(string16()); |
| autofill_icons_.push_back(string16()); |
| autofill_unique_ids_.push_back(0); // 0 means no profile. |
| } |
| } |
| } |
| |
| Send(new AutofillMsg_SuggestionsReturned(routing_id(), |
| query_id_, |
| autofill_values_, |
| autofill_labels_, |
| autofill_icons_, |
| autofill_unique_ids_)); |
| |
| query_id_ = 0; |
| autofill_values_.clear(); |
| autofill_labels_.clear(); |
| autofill_icons_.clear(); |
| autofill_unique_ids_.clear(); |
| } |