diff options
author | georgey@chromium.org <georgey@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-02 22:01:20 +0000 |
---|---|---|
committer | georgey@chromium.org <georgey@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-02 22:01:20 +0000 |
commit | db16347063a2217641ddd992f1f029af24362a59 (patch) | |
tree | 9e74d716e342052d9c1989f13e110935d9a98763 /chrome/browser/autofill | |
parent | 4af869aded61930f3085f28548a89daf4ab2ef0f (diff) | |
download | chromium_src-db16347063a2217641ddd992f1f029af24362a59.zip chromium_src-db16347063a2217641ddd992f1f029af24362a59.tar.gz chromium_src-db16347063a2217641ddd992f1f029af24362a59.tar.bz2 |
Behaving nice with AutoFill servers: Adjusting upload rate, processing 500, 502 and 503 responses, etc.
TEST=Unit-tested + by setting up the response from AutoFill server.
BUG=39921
Review URL: http://codereview.chromium.org/1535011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43531 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autofill')
-rw-r--r-- | chrome/browser/autofill/autofill_download.cc | 124 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_download.h | 41 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_download_unittest.cc | 61 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager.cc | 17 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_xml_parser.h | 2 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_xml_parser_unittest.cc | 55 |
6 files changed, 263 insertions, 37 deletions
diff --git a/chrome/browser/autofill/autofill_download.cc b/chrome/browser/autofill/autofill_download.cc index d0ac8368..42d4a32 100644 --- a/chrome/browser/autofill/autofill_download.cc +++ b/chrome/browser/autofill/autofill_download.cc @@ -9,7 +9,11 @@ #include "base/logging.h" #include "base/rand_util.h" #include "base/stl_util-inl.h" +#include "chrome/browser/autofill/autofill_xml_parser.h" +#include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" +#include "chrome/common/pref_names.h" +#include "net/http/http_response_headers.h" #define DISABLED_REQUEST_URL "http://disabled" @@ -18,21 +22,26 @@ #else #define AUTO_FILL_QUERY_SERVER_REQUEST_URL DISABLED_REQUEST_URL #define AUTO_FILL_UPLOAD_SERVER_REQUEST_URL DISABLED_REQUEST_URL +#define AUTO_FILL_QUERY_SERVER_NAME_START_IN_HEADER "SOMESERVER/" #endif -namespace { -// We only send a fraction of the forms to upload server. -// The rate for positive/negative matches potentially could be different. -const double kAutoFillPositiveUploadRate = 0.01; -const double kAutoFillNegativeUploadRate = 0.01; -} // namespace - -AutoFillDownloadManager::AutoFillDownloadManager() - : observer_(NULL), - positive_upload_rate_(kAutoFillPositiveUploadRate), - negative_upload_rate_(kAutoFillNegativeUploadRate), +AutoFillDownloadManager::AutoFillDownloadManager(Profile* profile) + : profile_(profile), + observer_(NULL), + next_query_request_(base::Time::Now()), + next_upload_request_(base::Time::Now()), + positive_upload_rate_(0), + negative_upload_rate_(0), fetcher_id_for_unittest_(0), is_testing_(false) { + // |profile_| could be NULL in some unit-tests. + if (profile_) { + PrefService* preferences = profile_->GetPrefs(); + positive_upload_rate_ = + preferences->GetReal(prefs::kAutoFillPositiveUploadRate); + negative_upload_rate_ = + preferences->GetReal(prefs::kAutoFillNegativeUploadRate); + } } AutoFillDownloadManager::~AutoFillDownloadManager() { @@ -52,6 +61,10 @@ void AutoFillDownloadManager::SetObserver( bool AutoFillDownloadManager::StartQueryRequest( const ScopedVector<FormStructure>& forms) { + if (next_query_request_ > base::Time::Now()) { + // We are in back-off mode: do not do the request. + return false; + } std::string form_xml; FormStructure::EncodeQueryRequest(forms, &form_xml); @@ -70,15 +83,18 @@ bool AutoFillDownloadManager::StartQueryRequest( bool AutoFillDownloadManager::StartUploadRequest( const FormStructure& form, bool form_was_matched) { + if (next_upload_request_ > base::Time::Now()) { + // We are in back-off mode: do not do the request. + return false; + } + // Check if we need to upload form. - // TODO(georgey): adjust this values from returned XML. - double upload_rate = form_was_matched ? positive_upload_rate_ : - negative_upload_rate_; + double upload_rate = form_was_matched ? GetPositiveUploadRate() : + GetNegativeUploadRate(); if (base::RandDouble() > upload_rate) { LOG(INFO) << "AutoFillDownloadManager: Upload request is ignored"; - if (observer_) - observer_->OnUploadedAutoFillHeuristics(form.FormSignature()); - return true; + // If we ever need notification that upload was skipped, add it here. + return false; } std::string form_xml; form.EncodeUploadRequest(form_was_matched, &form_xml); @@ -109,6 +125,35 @@ bool AutoFillDownloadManager::CancelRequest( return false; } +double AutoFillDownloadManager::GetPositiveUploadRate() const { + return positive_upload_rate_; +} + +double AutoFillDownloadManager::GetNegativeUploadRate() const { + return negative_upload_rate_; +} + +void AutoFillDownloadManager::SetPositiveUploadRate(double rate) { + if (rate == positive_upload_rate_) + return; + positive_upload_rate_ = rate; + DCHECK_GE(rate, 0.0); + DCHECK_LE(rate, 1.0); + DCHECK(profile_); + PrefService* preferences = profile_->GetPrefs(); + preferences->SetReal(prefs::kAutoFillPositiveUploadRate, rate); +} + +void AutoFillDownloadManager::SetNegativeUploadRate(double rate) { + if (rate == negative_upload_rate_) + return; + negative_upload_rate_ = rate; + DCHECK_GE(rate, 0.0); + DCHECK_LE(rate, 1.0); + DCHECK(profile_); + PrefService* preferences = profile_->GetPrefs(); + preferences->SetReal(prefs::kAutoFillNegativeUploadRate, rate); +} bool AutoFillDownloadManager::StartRequest( const std::string& form_xml, @@ -131,6 +176,7 @@ bool AutoFillDownloadManager::StartRequest( URLFetcher::POST, this); url_fetchers_[fetcher] = request_data; + fetcher->set_automatcally_retry_on_5xx(false); fetcher->set_request_context(Profile::GetDefaultRequestContext()); fetcher->set_upload_data("text/plain", form_xml); fetcher->Start(); @@ -150,8 +196,39 @@ void AutoFillDownloadManager::OnURLFetchComplete(const URLFetcher* source, it->second.request_type == AutoFillDownloadManager::REQUEST_QUERY ? "query" : "upload"); const int kHttpResponseOk = 200; + const int kHttpInternalServerError = 500; + const int kHttpBadGateway = 502; + const int kHttpServiceUnavailable = 503; + DCHECK(it->second.form_signatures.size()); if (response_code != kHttpResponseOk) { + bool back_off = false; + std::string server_header; + switch (response_code) { + case kHttpBadGateway: + if (!source->response_headers()->EnumerateHeader(NULL, "server", + &server_header) || + StartsWithASCII(server_header.c_str(), + AUTO_FILL_QUERY_SERVER_NAME_START_IN_HEADER, + false) != 0) + break; + // Bad getaway was received from AutoFill servers. Fall through to back + // off. + case kHttpInternalServerError: + case kHttpServiceUnavailable: + back_off = true; + break; + } + + if (back_off) { + base::Time back_off_time(base::Time::Now() + source->backoff_delay()); + if (it->second.request_type == AutoFillDownloadManager::REQUEST_QUERY) { + next_query_request_ = back_off_time; + } else { + next_upload_request_ = back_off_time; + } + } + LOG(INFO) << "AutoFillDownloadManager: " << type_of_request << " request has failed with response" << response_code; if (observer_) { @@ -166,7 +243,17 @@ void AutoFillDownloadManager::OnURLFetchComplete(const URLFetcher* source, if (observer_) observer_->OnLoadedAutoFillHeuristics(it->second.form_signatures, data); } else { - // TODO(georgey): adjust upload probabilities. + double new_positive_upload_rate = 0; + double new_negative_upload_rate = 0; + AutoFillUploadXmlParser parse_handler(&new_positive_upload_rate, + &new_negative_upload_rate); + buzz::XmlParser parser(&parse_handler); + parser.Parse(data.data(), data.length(), true); + if (parse_handler.succeeded()) { + SetPositiveUploadRate(new_positive_upload_rate); + SetNegativeUploadRate(new_negative_upload_rate); + } + if (observer_) observer_->OnUploadedAutoFillHeuristics(it->second.form_signatures[0]); } @@ -175,4 +262,3 @@ void AutoFillDownloadManager::OnURLFetchComplete(const URLFetcher* source, url_fetchers_.erase(it); } - diff --git a/chrome/browser/autofill/autofill_download.h b/chrome/browser/autofill/autofill_download.h index 620ac6f..a3511dd 100644 --- a/chrome/browser/autofill/autofill_download.h +++ b/chrome/browser/autofill/autofill_download.h @@ -12,11 +12,14 @@ #include "base/scoped_ptr.h" #include "base/scoped_vector.h" #include "base/string16.h" +#include "base/time.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/autofill/field_types.h" #include "chrome/browser/autofill/form_structure.h" #include "chrome/browser/net/url_fetcher.h" +class Profile; + // Handles getting and updating AutoFill heuristics. class AutoFillDownloadManager : public URLFetcher::Delegate { public: @@ -25,6 +28,7 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { REQUEST_UPLOAD, }; // An interface used to notify clients of AutoFillDownloadManager. + // Notifications are *not* guaranteed to be called. class Observer { public: // Called when heuristic successfully received from server. @@ -49,7 +53,8 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { virtual ~Observer() {} }; - AutoFillDownloadManager(); + // |profile| can be NULL in unit-tests only. + explicit AutoFillDownloadManager(Profile* profile); virtual ~AutoFillDownloadManager(); // |observer| - observer to notify on successful completion or error. @@ -61,8 +66,8 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { bool StartQueryRequest(const ScopedVector<FormStructure>& forms); // Start upload request if necessary. The probability of request going - // over the wire are |positive_upload_rate_| if it was matched by - // AutoFill, |negative_download_rate_| otherwise. Observer will be called + // over the wire are GetPositiveUploadRate() if it was matched by + // AutoFill, GetNegativeUploadRate() otherwise. Observer will be called // even if there was no actual trip over the wire. // |form| - form sent in this request. // |form_was_matched| - true if form was matched by the AutoFill. @@ -76,15 +81,17 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { bool CancelRequest(const std::string& form_signature, AutoFillRequestType request_type); - void SetPositiveUploadRate(double rate) { - DCHECK(rate >= 0.0 && rate <= 1.0); - positive_upload_rate_ = rate; - } - - void SetNegativeUploadRate(double rate) { - DCHECK(rate >= 0.0 && rate <= 1.0); - negative_upload_rate_ = rate; - } + // Probability of the form upload. Between 0 (no upload) and 1 (upload all). + // GetPositiveUploadRate() is for matched forms, + // GetNegativeUploadRate() for non matched. + double GetPositiveUploadRate() const; + double GetNegativeUploadRate() const; + // These functions called very rarely outside of theunit-tests. With current + // percentages, they would be called once per 100 auto-fillable forms filled + // and submitted by user. The order of magnitude would remain similar in the + // future. + void SetPositiveUploadRate(double rate); + void SetNegativeUploadRate(double rate); private: friend class AutoFillDownloadTestHelper; // unit-test. @@ -110,13 +117,21 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { const ResponseCookies& cookies, const std::string& data); + // Profile for preference storage. + Profile* profile_; + // For each requested form for both query and upload we create a separate // request and save its info. As url fetcher is identified by its address // we use a map between fetchers and info. std::map<URLFetcher*, FormRequestData> url_fetchers_; AutoFillDownloadManager::Observer *observer_; - // Probability of the form upload. Between 0 (no upload) and 1 (upload all). + // Time when next query/upload requests are allowed. If 50x HTTP received, + // exponential back off is initiated, so this times will be in the future + // for awhile. + base::Time next_query_request_; + base::Time next_upload_request_; + // |positive_upload_rate_| is for matched forms, // |negative_upload_rate_| for non matched. double positive_upload_rate_; diff --git a/chrome/browser/autofill/autofill_download_unittest.cc b/chrome/browser/autofill/autofill_download_unittest.cc index dcf692c..e1df3b8 100644 --- a/chrome/browser/autofill/autofill_download_unittest.cc +++ b/chrome/browser/autofill/autofill_download_unittest.cc @@ -7,6 +7,7 @@ #include "base/string_util.h" #include "chrome/browser/autofill/autofill_download.h" #include "chrome/browser/net/test_url_fetcher_factory.h" +#include "chrome/test/testing_profile.h" #include "net/url_request/url_request_status.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" @@ -24,7 +25,8 @@ using WebKit::WebInputElement; // successful upload request, failed upload request. class AutoFillDownloadTestHelper : public AutoFillDownloadManager::Observer { public: - AutoFillDownloadTestHelper() { + AutoFillDownloadTestHelper() + : download_manager(&profile) { download_manager.SetObserver(this); // For chromium builds forces Start*Request to actually execute. download_manager.is_testing_ = true; @@ -84,6 +86,7 @@ class AutoFillDownloadTestHelper : public AutoFillDownloadManager::Observer { }; std::list<AutoFillDownloadTestHelper::ResponseData> responses_; + TestingProfile profile; AutoFillDownloadManager download_manager; }; @@ -175,6 +178,10 @@ TEST(AutoFillDownloadTest, QueryAndUploadTest) { fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), URLRequestStatus(), 200, ResponseCookies(), std::string(responses[1])); + // After that upload rates would be adjusted to 0.5/0.3 + EXPECT_DOUBLE_EQ(0.5, helper.download_manager.GetPositiveUploadRate()); + EXPECT_DOUBLE_EQ(0.3, helper.download_manager.GetNegativeUploadRate()); + fetcher = factory.GetFetcherByID(2); ASSERT_TRUE(fetcher); fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), URLRequestStatus(), @@ -214,15 +221,63 @@ TEST(AutoFillDownloadTest, QueryAndUploadTest) { EXPECT_EQ(signature, helper.responses_.front().signature); EXPECT_EQ(responses[0], helper.responses_.front().response); helper.responses_.pop_front(); + // Set upload to 0% so no new requests happen. helper.download_manager.SetPositiveUploadRate(0.0); helper.download_manager.SetNegativeUploadRate(0.0); // No actual requests for the next two calls, as we set upload rate to 0%. - EXPECT_TRUE(helper.download_manager.StartUploadRequest(*(form_structures[0]), + EXPECT_FALSE(helper.download_manager.StartUploadRequest(*(form_structures[0]), true)); - EXPECT_TRUE(helper.download_manager.StartUploadRequest(*(form_structures[1]), + EXPECT_FALSE(helper.download_manager.StartUploadRequest(*(form_structures[1]), false)); + fetcher = factory.GetFetcherByID(3); + EXPECT_EQ(NULL, fetcher); + + // Verify DOS attack back-offs. + const int kBackOffTimeout = 10000; + + // Request with id 3. + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures)); + fetcher = factory.GetFetcherByID(3); + ASSERT_TRUE(fetcher); + fetcher->set_backoff_delay( + base::TimeDelta::FromMilliseconds(kBackOffTimeout)); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), URLRequestStatus(), + 500, ResponseCookies(), + std::string(responses[0])); + EXPECT_EQ(AutoFillDownloadTestHelper::REQUEST_QUERY_FAILED, + helper.responses_.front().type_of_response); + EXPECT_EQ(500, helper.responses_.front().error); + // Expected response on non-query request is an empty string. + EXPECT_EQ(std::string(""), helper.responses_.front().response); + helper.responses_.pop_front(); + + // Query requests should be ignored for the next 10 seconds. + EXPECT_FALSE(helper.download_manager.StartQueryRequest(form_structures)); + fetcher = factory.GetFetcherByID(4); + EXPECT_EQ(NULL, fetcher); + + // Set upload to 100% so requests happen. + helper.download_manager.SetPositiveUploadRate(1.0); + // Request with id 4. + EXPECT_TRUE(helper.download_manager.StartUploadRequest(*(form_structures[0]), + true)); fetcher = factory.GetFetcherByID(4); + ASSERT_TRUE(fetcher); + fetcher->set_backoff_delay( + base::TimeDelta::FromMilliseconds(kBackOffTimeout)); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), URLRequestStatus(), + 503, ResponseCookies(), + std::string(responses[2])); + EXPECT_EQ(AutoFillDownloadTestHelper::REQUEST_UPLOAD_FAILED, + helper.responses_.front().type_of_response); + EXPECT_EQ(503, helper.responses_.front().error); + helper.responses_.pop_front(); + + // Upload requests should be ignored for the next 10 seconds. + EXPECT_FALSE(helper.download_manager.StartUploadRequest(*(form_structures[0]), + true)); + fetcher = factory.GetFetcherByID(5); EXPECT_EQ(NULL, fetcher); // Make sure consumer of URLFetcher does the right thing. diff --git a/chrome/browser/autofill/autofill_manager.cc b/chrome/browser/autofill/autofill_manager.cc index 628cb9a..5bed021 100644 --- a/chrome/browser/autofill/autofill_manager.cc +++ b/chrome/browser/autofill/autofill_manager.cc @@ -21,9 +21,18 @@ #include "webkit/glue/form_field.h" #include "webkit/glue/form_field_values.h" +namespace { +// We only send a fraction of the forms to upload server. +// The rate for positive/negative matches potentially could be different. +const double kAutoFillPositiveUploadRateDefaultValue = 0.01; +const double kAutoFillNegativeUploadRateDefaultValue = 0.01; +} // namespace + + AutoFillManager::AutoFillManager(TabContents* tab_contents) : tab_contents_(tab_contents), personal_data_(NULL), + download_manager_(tab_contents_->profile()), infobar_(NULL) { DCHECK(tab_contents); @@ -52,6 +61,11 @@ void AutoFillManager::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kAutoFillAuxiliaryProfilesEnabled, false); prefs->RegisterStringPref(prefs::kAutoFillDefaultProfile, std::wstring()); prefs->RegisterStringPref(prefs::kAutoFillDefaultCreditCard, std::wstring()); + + prefs->RegisterRealPref(prefs::kAutoFillPositiveUploadRate, + kAutoFillPositiveUploadRateDefaultValue); + prefs->RegisterRealPref(prefs::kAutoFillNegativeUploadRate, + kAutoFillNegativeUploadRateDefaultValue); } void AutoFillManager::FormFieldValuesSubmitted( @@ -461,5 +475,6 @@ bool AutoFillManager::IsAutoFillEnabled() { AutoFillManager::AutoFillManager() : tab_contents_(NULL), - personal_data_(NULL) { + personal_data_(NULL), + download_manager_(NULL) { } diff --git a/chrome/browser/autofill/autofill_xml_parser.h b/chrome/browser/autofill/autofill_xml_parser.h index 1fda2fe..69c662a 100644 --- a/chrome/browser/autofill/autofill_xml_parser.h +++ b/chrome/browser/autofill/autofill_xml_parser.h @@ -100,7 +100,7 @@ class AutoFillQueryXmlParser : public AutoFillXmlParser { // the form matches what's in the users profile. // The negative upload rate is typically much lower than the positive upload // rate. -class AutoFillUploadXmlParser : public buzz::XmlParseHandler { +class AutoFillUploadXmlParser : public AutoFillXmlParser { public: AutoFillUploadXmlParser(double* positive_upload_rate, double* negative_upload_rate); diff --git a/chrome/browser/autofill/autofill_xml_parser_unittest.cc b/chrome/browser/autofill/autofill_xml_parser_unittest.cc index 7e1e55c..c06df83 100644 --- a/chrome/browser/autofill/autofill_xml_parser_unittest.cc +++ b/chrome/browser/autofill/autofill_xml_parser_unittest.cc @@ -135,4 +135,59 @@ TEST(AutoFillQueryXmlParserTest, ParseErrors) { EXPECT_EQ(NO_SERVER_DATA, field_types[0]); } +// Test successfull upload response. +TEST(AutoFillUploadXmlParser, TestSuccessfulResponse) { + std::string xml = "<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\"/>"; + double positive = 0; + double negative = 0; + AutoFillUploadXmlParser parse_handler(&positive, &negative); + buzz::XmlParser parser(&parse_handler); + parser.Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler.succeeded()); + EXPECT_DOUBLE_EQ(0.5, positive); + EXPECT_DOUBLE_EQ(0.3, negative); +} + +// Test failed upload response. +TEST(AutoFillUploadXmlParser, TestFailedResponse) { + std::string xml = "<autofilluploadresponse positiveuploadrate=\"\" " + "negativeuploadrate=\"0.3\"/>"; + double positive = 0; + double negative = 0; + scoped_ptr<AutoFillUploadXmlParser> parse_handler( + new AutoFillUploadXmlParser(&positive, &negative)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0.3, negative); // Partially parsed. + negative = 0; + + xml = "<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\""; + parse_handler.reset(new AutoFillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); + + xml = "bad data"; + parse_handler.reset(new AutoFillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); + + xml = ""; + parse_handler.reset(new AutoFillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); +} + } // namespace |