summaryrefslogtreecommitdiffstats
path: root/chromium/google_apis
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/google_apis
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (diff)
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/google_apis')
-rw-r--r--chromium/google_apis/BUILD.gn246
-rw-r--r--chromium/google_apis/OWNERS1
-rw-r--r--chromium/google_apis/cup/client_update_protocol_nss.cc7
-rw-r--r--chromium/google_apis/drive/auth_service.cc3
-rw-r--r--chromium/google_apis/drive/base_requests.cc3
-rw-r--r--chromium/google_apis/drive/base_requests_unittest.cc2
-rw-r--r--chromium/google_apis/drive/drive_api_parser.cc107
-rw-r--r--chromium/google_apis/drive/drive_api_parser.h175
-rw-r--r--chromium/google_apis/drive/drive_api_parser_unittest.cc68
-rw-r--r--chromium/google_apis/drive/drive_api_requests.cc184
-rw-r--r--chromium/google_apis/drive/drive_api_requests.h165
-rw-r--r--chromium/google_apis/drive/drive_api_requests_unittest.cc208
-rw-r--r--chromium/google_apis/drive/drive_api_url_generator.cc58
-rw-r--r--chromium/google_apis/drive/drive_api_url_generator.h19
-rw-r--r--chromium/google_apis/drive/drive_api_url_generator_unittest.cc70
-rw-r--r--chromium/google_apis/drive/drive_common_callbacks.h5
-rw-r--r--chromium/google_apis/drive/gdata_contacts_requests.cc115
-rw-r--r--chromium/google_apis/drive/gdata_contacts_requests.h102
-rw-r--r--chromium/google_apis/drive/gdata_wapi_parser.cc248
-rw-r--r--chromium/google_apis/drive/gdata_wapi_parser.h247
-rw-r--r--chromium/google_apis/drive/gdata_wapi_parser_unittest.cc69
-rw-r--r--chromium/google_apis/drive/gdata_wapi_requests.cc647
-rw-r--r--chromium/google_apis/drive/gdata_wapi_requests.h442
-rw-r--r--chromium/google_apis/drive/gdata_wapi_requests_unittest.cc1427
-rw-r--r--chromium/google_apis/drive/gdata_wapi_url_generator.cc185
-rw-r--r--chromium/google_apis/drive/gdata_wapi_url_generator.h83
-rw-r--r--chromium/google_apis/drive/gdata_wapi_url_generator_unittest.cc170
-rw-r--r--chromium/google_apis/drive/task_util.cc4
-rw-r--r--chromium/google_apis/drive/task_util.h15
-rw-r--r--chromium/google_apis/drive/test_util.cc3
-rw-r--r--chromium/google_apis/gaia/DEPS5
-rw-r--r--chromium/google_apis/gaia/account_tracker.cc293
-rw-r--r--chromium/google_apis/gaia/account_tracker.h149
-rw-r--r--chromium/google_apis/gaia/account_tracker_unittest.cc816
-rw-r--r--chromium/google_apis/gaia/fake_gaia.cc531
-rw-r--r--chromium/google_apis/gaia/fake_gaia.h102
-rw-r--r--chromium/google_apis/gaia/fake_identity_provider.cc40
-rw-r--r--chromium/google_apis/gaia/fake_identity_provider.h38
-rw-r--r--chromium/google_apis/gaia/fake_oauth2_token_service.cc89
-rw-r--r--chromium/google_apis/gaia/fake_oauth2_token_service.h84
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher.cc179
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher.h19
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc59
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util.cc72
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util.h16
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util_unittest.cc90
-rw-r--r--chromium/google_apis/gaia/gaia_constants.cc7
-rw-r--r--chromium/google_apis/gaia/gaia_constants.h4
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client.cc7
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client.h2
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client_unittest.cc10
-rw-r--r--chromium/google_apis/gaia/gaia_switches.cc6
-rw-r--r--chromium/google_apis/gaia/gaia_switches.h11
-rw-r--r--chromium/google_apis/gaia/gaia_urls.cc23
-rw-r--r--chromium/google_apis/gaia/gaia_urls.h6
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error.cc17
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error.h7
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error_unittest.cc6
-rw-r--r--chromium/google_apis/gaia/identity_provider.cc71
-rw-r--r--chromium/google_apis/gaia/identity_provider.h93
-rw-r--r--chromium/google_apis/gaia/merge_session_helper.cc178
-rw-r--r--chromium/google_apis/gaia/merge_session_helper.h117
-rw-r--r--chromium/google_apis/gaia/merge_session_helper_unittest.cc307
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher.cc306
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher.h94
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.cc313
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.h118
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc (renamed from chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc)149
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow.cc4
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow.h2
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc44
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow.cc12
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow.h2
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc14
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service.cc121
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service.h72
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service_request.cc360
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service_request.h96
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service_request_unittest.cc263
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service_test_util.cc3
-rw-r--r--chromium/google_apis/gaia/oauth2_token_service_unittest.cc30
-rw-r--r--chromium/google_apis/gaia/ubertoken_fetcher.cc63
-rw-r--r--chromium/google_apis/gaia/ubertoken_fetcher.h71
-rw-r--r--chromium/google_apis/gaia/ubertoken_fetcher_unittest.cc112
-rw-r--r--chromium/google_apis/gcm/DEPS1
-rw-r--r--chromium/google_apis/gcm/base/encryptor.h27
-rw-r--r--chromium/google_apis/gcm/base/fake_encryptor.cc24
-rw-r--r--chromium/google_apis/gcm/base/fake_encryptor.h28
-rw-r--r--chromium/google_apis/gcm/base/mcs_message_unittest.cc6
-rw-r--r--chromium/google_apis/gcm/base/mcs_util.cc61
-rw-r--r--chromium/google_apis/gcm/base/mcs_util.h15
-rw-r--r--chromium/google_apis/gcm/base/mcs_util_unittest.cc6
-rw-r--r--chromium/google_apis/gcm/base/socket_stream.cc4
-rw-r--r--chromium/google_apis/gcm/engine/checkin_request.cc221
-rw-r--r--chromium/google_apis/gcm/engine/checkin_request.h95
-rw-r--r--chromium/google_apis/gcm/engine/checkin_request_unittest.cc380
-rw-r--r--chromium/google_apis/gcm/engine/connection_factory.cc3
-rw-r--r--chromium/google_apis/gcm/engine/connection_factory.h51
-rw-r--r--chromium/google_apis/gcm/engine/connection_factory_impl.cc508
-rw-r--r--chromium/google_apis/gcm/engine/connection_factory_impl.h108
-rw-r--r--chromium/google_apis/gcm/engine/connection_factory_impl_unittest.cc364
-rw-r--r--chromium/google_apis/gcm/engine/connection_handler.h6
-rw-r--r--chromium/google_apis/gcm/engine/connection_handler_impl.cc67
-rw-r--r--chromium/google_apis/gcm/engine/connection_handler_impl.h5
-rw-r--r--chromium/google_apis/gcm/engine/connection_handler_impl_unittest.cc99
-rw-r--r--chromium/google_apis/gcm/engine/fake_connection_factory.cc22
-rw-r--r--chromium/google_apis/gcm/engine/fake_connection_factory.h16
-rw-r--r--chromium/google_apis/gcm/engine/fake_connection_handler.cc7
-rw-r--r--chromium/google_apis/gcm/engine/fake_connection_handler.h3
-rw-r--r--chromium/google_apis/gcm/engine/gcm_store.cc21
-rw-r--r--chromium/google_apis/gcm/engine/gcm_store.h121
-rw-r--r--chromium/google_apis/gcm/engine/gcm_store_impl.cc956
-rw-r--r--chromium/google_apis/gcm/engine/gcm_store_impl.h126
-rw-r--r--chromium/google_apis/gcm/engine/gcm_store_impl_unittest.cc552
-rw-r--r--chromium/google_apis/gcm/engine/gservices_settings.cc342
-rw-r--r--chromium/google_apis/gcm/engine/gservices_settings.h82
-rw-r--r--chromium/google_apis/gcm/engine/gservices_settings_unittest.cc336
-rw-r--r--chromium/google_apis/gcm/engine/heartbeat_manager.cc119
-rw-r--r--chromium/google_apis/gcm/engine/heartbeat_manager.h82
-rw-r--r--chromium/google_apis/gcm/engine/heartbeat_manager_unittest.cc176
-rw-r--r--chromium/google_apis/gcm/engine/mcs_client.cc605
-rw-r--r--chromium/google_apis/gcm/engine/mcs_client.h150
-rw-r--r--chromium/google_apis/gcm/engine/mcs_client_unittest.cc479
-rw-r--r--chromium/google_apis/gcm/engine/registration_info.cc62
-rw-r--r--chromium/google_apis/gcm/engine/registration_info.h35
-rw-r--r--chromium/google_apis/gcm/engine/registration_request.cc267
-rw-r--r--chromium/google_apis/gcm/engine/registration_request.h128
-rw-r--r--chromium/google_apis/gcm/engine/registration_request_unittest.cc427
-rw-r--r--chromium/google_apis/gcm/engine/rmq_store.cc491
-rw-r--r--chromium/google_apis/gcm/engine/rmq_store.h102
-rw-r--r--chromium/google_apis/gcm/engine/rmq_store_unittest.cc303
-rw-r--r--chromium/google_apis/gcm/engine/unregistration_request.cc224
-rw-r--r--chromium/google_apis/gcm/engine/unregistration_request.h115
-rw-r--r--chromium/google_apis/gcm/engine/unregistration_request_unittest.cc295
-rw-r--r--chromium/google_apis/gcm/gcm.gyp89
-rw-r--r--chromium/google_apis/gcm/gcm_client.cc45
-rw-r--r--chromium/google_apis/gcm/gcm_client.h193
-rw-r--r--chromium/google_apis/gcm/gcm_client_impl.cc39
-rw-r--r--chromium/google_apis/gcm/gcm_client_impl.h39
-rw-r--r--chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc107
-rw-r--r--chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h71
-rw-r--r--chromium/google_apis/gcm/monitoring/gcm_stats_recorder.h135
-rw-r--r--chromium/google_apis/gcm/protocol/android_checkin.proto97
-rw-r--r--chromium/google_apis/gcm/protocol/checkin.proto156
-rw-r--r--chromium/google_apis/gcm/tools/mcs_probe.cc191
-rw-r--r--chromium/google_apis/google_api_keys.cc19
-rw-r--r--chromium/google_apis/google_api_keys.h4
-rw-r--r--chromium/google_apis/google_api_keys_unittest.cc1
-rw-r--r--chromium/google_apis/google_apis.gyp106
149 files changed, 14004 insertions, 6792 deletions
diff --git a/chromium/google_apis/BUILD.gn b/chromium/google_apis/BUILD.gn
new file mode 100644
index 00000000000..6dbeab10039
--- /dev/null
+++ b/chromium/google_apis/BUILD.gn
@@ -0,0 +1,246 @@
+# Copyright 2014 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.
+
+import("//build/config/crypto.gni")
+
+declare_args() {
+ # You can set the variable 'use_official_google_api_keys' to true
+ # to use the Google-internal file containing official API keys
+ # for Google Chrome even in a developer build. Setting this
+ # variable explicitly to true will cause your build to fail if the
+ # internal file is missing.
+ #
+ # The variable is documented here, but not handled in this file;
+ # see //google_apis/determine_use_official_keys.gypi for the
+ # implementation.
+ #
+ # Set the variable to false to not use the internal file, even when
+ # it exists in your checkout.
+ #
+ # Leave it unset or set to "" to have the variable
+ # implicitly set to true if you have
+ # src/google_apis/internal/google_chrome_api_keys.h in your
+ # checkout, and implicitly set to false if not.
+ #
+ # Note that official builds always behave as if the variable
+ # was explicitly set to true, i.e. they always use official keys,
+ # and will fail to build if the internal file is missing.
+ use_official_google_api_keys = ""
+
+ # Set these to bake the specified API keys and OAuth client
+ # IDs/secrets into your build.
+ #
+ # If you create a build without values baked in, you can instead
+ # set environment variables to provide the keys at runtime (see
+ # src/google_apis/google_api_keys.h for details). Features that
+ # require server-side APIs may fail to work if no keys are
+ # provided.
+ #
+ # Note that if you are building an official build or if
+ # use_official_google_api_keys has been set to trie (explicitly or
+ # implicitly), these values will be ignored and the official
+ # keys will be used instead.
+ google_api_key = ""
+
+ # See google_api_key.
+ google_default_client_id = ""
+
+ # See google_api_key.
+ google_default_client_secret = ""
+}
+
+if (use_official_google_api_keys == "") {
+ # Default behavior, check if the key file exists.
+ check_internal_result = exec_script(
+ "build/check_internal.py",
+ [ rebase_path("internal/google_chrome_api_keys.h", root_build_dir) ],
+ "value")
+ use_official_google_api_keys = (check_internal_result == 1)
+}
+
+config("key_defines") {
+ defines = []
+ if (use_official_google_api_keys) {
+ defines += [ "USE_OFFICIAL_GOOGLE_API_KEYS=1" ]
+ }
+ if (google_api_key != "") {
+ defines += [ "GOOGLE_API_KEY=$google_api_key" ]
+ }
+ if (google_default_client_id != "") {
+ defines += [ "GOOGLE_DEFAULT_CLIENT_ID=$google_default_client_id" ]
+ }
+ if (google_default_client_secret != "") {
+ defines += [ "GOOGLE_DEFAULT_CLIENT_SECRET=$google_default_client_secret" ]
+ }
+}
+
+source_set("google_apis") {
+ sources = [
+ "cup/client_update_protocol.cc",
+ "cup/client_update_protocol.h",
+ "drive/auth_service.cc",
+ "drive/auth_service.h",
+ "drive/auth_service_interface.h",
+ "drive/auth_service_observer.h",
+ "drive/base_requests.cc",
+ "drive/base_requests.h",
+ "drive/drive_api_parser.cc",
+ "drive/drive_api_parser.h",
+ "drive/drive_api_requests.cc",
+ "drive/drive_api_requests.h",
+ "drive/drive_api_url_generator.cc",
+ "drive/drive_api_url_generator.h",
+ "drive/drive_common_callbacks.h",
+ "drive/drive_entry_kinds.h",
+ "drive/gdata_errorcode.cc",
+ "drive/gdata_errorcode.h",
+ "drive/gdata_wapi_requests.cc",
+ "drive/gdata_wapi_requests.h",
+ "drive/gdata_wapi_parser.cc",
+ "drive/gdata_wapi_parser.h",
+ "drive/gdata_wapi_url_generator.cc",
+ "drive/gdata_wapi_url_generator.h",
+ "drive/request_sender.cc",
+ "drive/request_sender.h",
+ "drive/request_util.cc",
+ "drive/request_util.h",
+ "drive/task_util.cc",
+ "drive/task_util.h",
+ "drive/time_util.cc",
+ "drive/time_util.h",
+ "gaia/gaia_auth_consumer.cc",
+ "gaia/gaia_auth_consumer.h",
+ "gaia/gaia_auth_fetcher.cc",
+ "gaia/gaia_auth_fetcher.h",
+ "gaia/gaia_auth_util.cc",
+ "gaia/gaia_auth_util.h",
+ "gaia/gaia_constants.cc",
+ "gaia/gaia_constants.h",
+ "gaia/gaia_oauth_client.cc",
+ "gaia/gaia_oauth_client.h",
+ "gaia/gaia_switches.cc",
+ "gaia/gaia_switches.h",
+ "gaia/gaia_urls.cc",
+ "gaia/gaia_urls.h",
+ "gaia/google_service_auth_error.cc",
+ "gaia/google_service_auth_error.h",
+ "gaia/identity_provider.cc",
+ "gaia/identity_provider.h",
+ "gaia/merge_session_helper.cc",
+ "gaia/merge_session_helper.h",
+ "gaia/oauth_request_signer.cc",
+ "gaia/oauth_request_signer.h",
+ "gaia/oauth2_access_token_consumer.h",
+ "gaia/oauth2_access_token_fetcher.h",
+ "gaia/oauth2_access_token_fetcher.cc",
+ "gaia/oauth2_access_token_fetcher_impl.cc",
+ "gaia/oauth2_access_token_fetcher_impl.h",
+ "gaia/oauth2_api_call_flow.cc",
+ "gaia/oauth2_api_call_flow.h",
+ "gaia/oauth2_mint_token_flow.cc",
+ "gaia/oauth2_mint_token_flow.h",
+ "gaia/oauth2_token_service.cc",
+ "gaia/oauth2_token_service.h",
+ "gaia/ubertoken_fetcher.cc",
+ "gaia/ubertoken_fetcher.h",
+ "google_api_keys.cc",
+ "google_api_keys.h",
+ ]
+
+ if (is_win) {
+ cflags = [ "/wd4267" ] # size_t -> int
+ }
+
+ configs += [ ":key_defines" ]
+
+ deps = [
+ "//base",
+ "//crypto",
+ "//crypto:platform",
+ "//net",
+ "//third_party/libxml",
+ ]
+
+ if (use_openssl) {
+ sources += [ "cup/client_update_protocol_openssl.cc" ]
+ } else {
+ sources += [ "cup/client_update_protocol_nss.cc" ]
+ }
+}
+
+source_set("test_support") {
+ sources = [
+ "drive/dummy_auth_service.cc",
+ "drive/dummy_auth_service.h",
+ "drive/test_util.cc",
+ "drive/test_util.h",
+ "gaia/fake_gaia.cc",
+ "gaia/fake_gaia.h",
+ "gaia/fake_identity_provider.cc",
+ "gaia/fake_identity_provider.h",
+ "gaia/fake_oauth2_token_service.cc",
+ "gaia/fake_oauth2_token_service.h",
+ "gaia/mock_url_fetcher_factory.h",
+ "gaia/oauth2_token_service_test_util.cc",
+ "gaia/oauth2_token_service_test_util.h",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//net",
+ "//net:test_support",
+ ]
+ forward_dependent_configs_from = deps
+}
+
+test("google_apis_unittest") {
+ sources = [
+ "google_api_keys_unittest.cc",
+ "cup/client_update_protocol_unittest.cc",
+ "drive/base_requests_unittest.cc",
+ "drive/base_requests_server_unittest.cc",
+ "drive/drive_api_requests_unittest.cc",
+ "drive/drive_api_parser_unittest.cc",
+ "drive/drive_api_url_generator_unittest.cc",
+ "drive/gdata_wapi_parser_unittest.cc",
+ "drive/gdata_wapi_requests_unittest.cc",
+ "drive/gdata_wapi_url_generator_unittest.cc",
+ "drive/request_sender_unittest.cc",
+ "drive/request_util_unittest.cc",
+ "drive/time_util_unittest.cc",
+ "gaia/gaia_auth_fetcher_unittest.cc",
+ "gaia/gaia_auth_util_unittest.cc",
+ "gaia/gaia_oauth_client_unittest.cc",
+ "gaia/google_service_auth_error_unittest.cc",
+ "gaia/merge_session_helper_unittest.cc",
+ "gaia/oauth_request_signer_unittest.cc",
+ "gaia/oauth2_access_token_fetcher_impl_unittest.cc",
+ "gaia/oauth2_api_call_flow_unittest.cc",
+ "gaia/oauth2_mint_token_flow_unittest.cc",
+ "gaia/oauth2_token_service_unittest.cc",
+ "gaia/ubertoken_fetcher_unittest.cc",
+ ]
+
+ configs += [ ":key_defines" ]
+
+ deps = [
+ ":google_apis",
+ ":test_support",
+ "//base",
+ "//base/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ if (is_android) {
+ sources -= [
+ "drive/base_requests_server_unittest.cc",
+ "drive/drive_api_parser_unittest.cc",
+ "drive/drive_api_requests_unittest.cc",
+ "drive/gdata_wapi_parser_unittest.cc",
+ "drive/gdata_wapi_requests_unittest.cc",
+ ]
+ }
+}
diff --git a/chromium/google_apis/OWNERS b/chromium/google_apis/OWNERS
index e3221f4c57c..258354a3415 100644
--- a/chromium/google_apis/OWNERS
+++ b/chromium/google_apis/OWNERS
@@ -1,2 +1 @@
-joi@chromium.org
rogerta@chromium.org
diff --git a/chromium/google_apis/cup/client_update_protocol_nss.cc b/chromium/google_apis/cup/client_update_protocol_nss.cc
index b369c13d154..1d3ff24d4f7 100644
--- a/chromium/google_apis/cup/client_update_protocol_nss.cc
+++ b/chromium/google_apis/cup/client_update_protocol_nss.cc
@@ -12,10 +12,9 @@
#include "crypto/nss_util.h"
#include "crypto/scoped_nss_types.h"
-typedef scoped_ptr_malloc<
- CERTSubjectPublicKeyInfo,
- crypto::NSSDestroyer<CERTSubjectPublicKeyInfo,
- SECKEY_DestroySubjectPublicKeyInfo> >
+typedef scoped_ptr<CERTSubjectPublicKeyInfo,
+ crypto::NSSDestroyer<CERTSubjectPublicKeyInfo,
+ SECKEY_DestroySubjectPublicKeyInfo> >
ScopedCERTSubjectPublicKeyInfo;
ClientUpdateProtocol::~ClientUpdateProtocol() {
diff --git a/chromium/google_apis/drive/auth_service.cc b/chromium/google_apis/drive/auth_service.cc
index 18623686f94..51229f92b6a 100644
--- a/chromium/google_apis/drive/auth_service.cc
+++ b/chromium/google_apis/drive/auth_service.cc
@@ -58,7 +58,8 @@ AuthRequest::AuthRequest(
net::URLRequestContextGetter* url_request_context_getter,
const AuthStatusCallback& callback,
const std::vector<std::string>& scopes)
- : callback_(callback) {
+ : OAuth2TokenService::Consumer("auth_service"),
+ callback_(callback) {
DCHECK(!callback_.is_null());
request_ = oauth2_token_service->
StartRequestWithContext(
diff --git a/chromium/google_apis/drive/base_requests.cc b/chromium/google_apis/drive/base_requests.cc
index be64f783c24..0c1a238057e 100644
--- a/chromium/google_apis/drive/base_requests.cc
+++ b/chromium/google_apis/drive/base_requests.cc
@@ -358,6 +358,7 @@ void UrlFetchRequestBase::OnURLFetchComplete(const URLFetcher* source) {
const char kErrorMessageKey[] = "message";
const char kErrorReasonRateLimitExceeded[] = "rateLimitExceeded";
const char kErrorReasonUserRateLimitExceeded[] = "userRateLimitExceeded";
+ const char kErrorReasonQuotaExceeded[] = "quotaExceeded";
scoped_ptr<base::Value> value(ParseJsonInternal(response_writer_->data()));
base::DictionaryValue* dictionary = NULL;
@@ -380,6 +381,8 @@ void UrlFetchRequestBase::OnURLFetchComplete(const URLFetcher* source) {
if (reason == kErrorReasonRateLimitExceeded ||
reason == kErrorReasonUserRateLimitExceeded)
error_code_ = HTTP_SERVICE_UNAVAILABLE;
+ if (reason == kErrorReasonQuotaExceeded)
+ error_code_ = GDATA_NO_SPACE;
}
}
}
diff --git a/chromium/google_apis/drive/base_requests_unittest.cc b/chromium/google_apis/drive/base_requests_unittest.cc
index 3032d2abb37..f3a97c19156 100644
--- a/chromium/google_apis/drive/base_requests_unittest.cc
+++ b/chromium/google_apis/drive/base_requests_unittest.cc
@@ -115,7 +115,7 @@ TEST_F(BaseRequestsTest, ParseValidJson) {
base::Bind(test_util::CreateCopyResultCallback(&json)));
base::RunLoop().RunUntilIdle();
- DictionaryValue* root_dict = NULL;
+ base::DictionaryValue* root_dict = NULL;
ASSERT_TRUE(json);
ASSERT_TRUE(json->GetAsDictionary(&root_dict));
diff --git a/chromium/google_apis/drive/drive_api_parser.cc b/chromium/google_apis/drive/drive_api_parser.cc
index 4b05e8b5844..c22070eb458 100644
--- a/chromium/google_apis/drive/drive_api_parser.cc
+++ b/chromium/google_apis/drive/drive_api_parser.cc
@@ -38,6 +38,28 @@ bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
return true;
}
+// Converts |value| to |result|.
+bool GetParentsFromValue(const base::Value* value,
+ std::vector<ParentReference>* result) {
+ DCHECK(value);
+ DCHECK(result);
+
+ const base::ListValue* list_value = NULL;
+ if (!value->GetAsList(&list_value))
+ return false;
+
+ base::JSONValueConverter<ParentReference> converter;
+ result->resize(list_value->GetSize());
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ const base::Value* parent_value = NULL;
+ if (!list_value->Get(i, &parent_value) ||
+ !converter.Convert(*parent_value, &(*result)[i]))
+ return false;
+ }
+
+ return true;
+}
+
// Converts |value| to |result|. The key of |value| is app_id, and its value
// is URL to open the resource on the web app.
bool GetOpenWithLinksFromDictionaryValue(
@@ -75,7 +97,6 @@ bool GetOpenWithLinksFromDictionaryValue(
const char kKind[] = "kind";
const char kId[] = "id";
const char kETag[] = "etag";
-const char kSelfLink[] = "selfLink";
const char kItems[] = "items";
const char kLargestChangeId[] = "largestChangeId";
@@ -97,16 +118,15 @@ const char kIconUrl[] = "iconUrl";
const char kAppKind[] = "drive#app";
const char kName[] = "name";
const char kObjectType[] = "objectType";
+const char kProductId[] = "productId";
const char kSupportsCreate[] = "supportsCreate";
-const char kSupportsImport[] = "supportsImport";
-const char kInstalled[] = "installed";
-const char kAuthorized[] = "authorized";
-const char kProductUrl[] = "productUrl";
+const char kRemovable[] = "removable";
const char kPrimaryMimeTypes[] = "primaryMimeTypes";
const char kSecondaryMimeTypes[] = "secondaryMimeTypes";
const char kPrimaryFileExtensions[] = "primaryFileExtensions";
const char kSecondaryFileExtensions[] = "secondaryFileExtensions";
const char kIcons[] = "icons";
+const char kCreateUrl[] = "createUrl";
// Apps List
// https://developers.google.com/drive/v2/reference/apps/list
@@ -116,7 +136,6 @@ const char kAppListKind[] = "drive#appList";
// https://developers.google.com/drive/v2/reference/parents
const char kParentReferenceKind[] = "drive#parentReference";
const char kParentLink[] = "parentLink";
-const char kIsRoot[] = "isRoot";
// File Resource
// https://developers.google.com/drive/v2/reference/files
@@ -124,29 +143,20 @@ const char kFileKind[] = "drive#file";
const char kTitle[] = "title";
const char kMimeType[] = "mimeType";
const char kCreatedDate[] = "createdDate";
+const char kModificationDate[] = "modificationDate";
const char kModifiedDate[] = "modifiedDate";
-const char kModifiedByMeDate[] = "modifiedByMeDate";
const char kLastViewedByMeDate[] = "lastViewedByMeDate";
const char kSharedWithMeDate[] = "sharedWithMeDate";
-const char kDownloadUrl[] = "downloadUrl";
-const char kFileExtension[] = "fileExtension";
const char kMd5Checksum[] = "md5Checksum";
const char kFileSize[] = "fileSize";
const char kAlternateLink[] = "alternateLink";
-const char kEmbedLink[] = "embedLink";
const char kParents[] = "parents";
-const char kThumbnailLink[] = "thumbnailLink";
-const char kWebContentLink[] = "webContentLink";
const char kOpenWithLinks[] = "openWithLinks";
const char kLabels[] = "labels";
const char kImageMediaMetadata[] = "imageMediaMetadata";
const char kShared[] = "shared";
// These 5 flags are defined under |labels|.
-const char kLabelStarred[] = "starred";
-const char kLabelHidden[] = "hidden";
const char kLabelTrashed[] = "trashed";
-const char kLabelRestricted[] = "restricted";
-const char kLabelViewed[] = "viewed";
// These 3 flags are defined under |imageMediaMetadata|.
const char kImageMediaMetadataWidth[] = "width";
const char kImageMediaMetadataHeight[] = "height";
@@ -157,7 +167,6 @@ const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
// Files List
// https://developers.google.com/drive/v2/reference/files/list
const char kFileListKind[] = "drive#fileList";
-const char kNextPageToken[] = "nextPageToken";
const char kNextLink[] = "nextLink";
// Change Resource
@@ -299,9 +308,7 @@ bool DriveAppIcon::GetIconCategory(const base::StringPiece& category,
AppResource::AppResource()
: supports_create_(false),
- supports_import_(false),
- installed_(false),
- authorized_(false) {
+ removable_(false) {
}
AppResource::~AppResource() {}
@@ -312,13 +319,9 @@ void AppResource::RegisterJSONConverter(
converter->RegisterStringField(kId, &AppResource::application_id_);
converter->RegisterStringField(kName, &AppResource::name_);
converter->RegisterStringField(kObjectType, &AppResource::object_type_);
+ converter->RegisterStringField(kProductId, &AppResource::product_id_);
converter->RegisterBoolField(kSupportsCreate, &AppResource::supports_create_);
- converter->RegisterBoolField(kSupportsImport, &AppResource::supports_import_);
- converter->RegisterBoolField(kInstalled, &AppResource::installed_);
- converter->RegisterBoolField(kAuthorized, &AppResource::authorized_);
- converter->RegisterCustomField<GURL>(kProductUrl,
- &AppResource::product_url_,
- GetGURLFromString);
+ converter->RegisterBoolField(kRemovable, &AppResource::removable_);
converter->RegisterRepeatedString(kPrimaryMimeTypes,
&AppResource::primary_mimetypes_);
converter->RegisterRepeatedString(kSecondaryMimeTypes,
@@ -328,6 +331,9 @@ void AppResource::RegisterJSONConverter(
converter->RegisterRepeatedString(kSecondaryFileExtensions,
&AppResource::secondary_file_extensions_);
converter->RegisterRepeatedMessage(kIcons, &AppResource::icons_);
+ converter->RegisterCustomField<GURL>(kCreateUrl,
+ &AppResource::create_url_,
+ GetGURLFromString);
}
// static
@@ -386,7 +392,7 @@ bool AppList::Parse(const base::Value& value) {
////////////////////////////////////////////////////////////////////////////////
// ParentReference implementation
-ParentReference::ParentReference() : is_root_(false) {}
+ParentReference::ParentReference() {}
ParentReference::~ParentReference() {}
@@ -397,7 +403,6 @@ void ParentReference::RegisterJSONConverter(
converter->RegisterCustomField<GURL>(kParentLink,
&ParentReference::parent_link_,
GetGURLFromString);
- converter->RegisterBoolField(kIsRoot, &ParentReference::is_root_);
}
// static
@@ -433,9 +438,6 @@ void FileResource::RegisterJSONConverter(
base::JSONValueConverter<FileResource>* converter) {
converter->RegisterStringField(kId, &FileResource::file_id_);
converter->RegisterStringField(kETag, &FileResource::etag_);
- converter->RegisterCustomField<GURL>(kSelfLink,
- &FileResource::self_link_,
- GetGURLFromString);
converter->RegisterStringField(kTitle, &FileResource::title_);
converter->RegisterStringField(kMimeType, &FileResource::mime_type_);
converter->RegisterNestedField(kLabels, &FileResource::labels_);
@@ -450,10 +452,6 @@ void FileResource::RegisterJSONConverter(
&FileResource::modified_date_,
&util::GetTimeFromString);
converter->RegisterCustomField<base::Time>(
- kModifiedByMeDate,
- &FileResource::modified_by_me_date_,
- &util::GetTimeFromString);
- converter->RegisterCustomField<base::Time>(
kLastViewedByMeDate,
&FileResource::last_viewed_by_me_date_,
&util::GetTimeFromString);
@@ -462,11 +460,6 @@ void FileResource::RegisterJSONConverter(
&FileResource::shared_with_me_date_,
&util::GetTimeFromString);
converter->RegisterBoolField(kShared, &FileResource::shared_);
- converter->RegisterCustomField<GURL>(kDownloadUrl,
- &FileResource::download_url_,
- GetGURLFromString);
- converter->RegisterStringField(kFileExtension,
- &FileResource::file_extension_);
converter->RegisterStringField(kMd5Checksum, &FileResource::md5_checksum_);
converter->RegisterCustomField<int64>(kFileSize,
&FileResource::file_size_,
@@ -474,17 +467,10 @@ void FileResource::RegisterJSONConverter(
converter->RegisterCustomField<GURL>(kAlternateLink,
&FileResource::alternate_link_,
GetGURLFromString);
- converter->RegisterCustomField<GURL>(kEmbedLink,
- &FileResource::embed_link_,
- GetGURLFromString);
- converter->RegisterRepeatedMessage<ParentReference>(kParents,
- &FileResource::parents_);
- converter->RegisterCustomField<GURL>(kThumbnailLink,
- &FileResource::thumbnail_link_,
- GetGURLFromString);
- converter->RegisterCustomField<GURL>(kWebContentLink,
- &FileResource::web_content_link_,
- GetGURLFromString);
+ converter->RegisterCustomValueField<std::vector<ParentReference> >(
+ kParents,
+ &FileResource::parents_,
+ GetParentsFromValue);
converter->RegisterCustomValueField<std::vector<OpenWithLink> >(
kOpenWithLinks,
&FileResource::open_with_links_,
@@ -524,8 +510,6 @@ FileList::~FileList() {}
// static
void FileList::RegisterJSONConverter(
base::JSONValueConverter<FileList>* converter) {
- converter->RegisterStringField(kETag, &FileList::etag_);
- converter->RegisterStringField(kNextPageToken, &FileList::next_page_token_);
converter->RegisterCustomField<GURL>(kNextLink,
&FileList::next_link_,
GetGURLFromString);
@@ -574,6 +558,9 @@ void ChangeResource::RegisterJSONConverter(
converter->RegisterBoolField(kDeleted, &ChangeResource::deleted_);
converter->RegisterCustomValueField(kFile, &ChangeResource::file_,
&CreateFileResourceFromValue);
+ converter->RegisterCustomField<base::Time>(
+ kModificationDate, &ChangeResource::modification_date_,
+ &util::GetTimeFromString);
}
// static
@@ -606,8 +593,6 @@ ChangeList::~ChangeList() {}
// static
void ChangeList::RegisterJSONConverter(
base::JSONValueConverter<ChangeList>* converter) {
- converter->RegisterStringField(kETag, &ChangeList::etag_);
- converter->RegisterStringField(kNextPageToken, &ChangeList::next_page_token_);
converter->RegisterCustomField<GURL>(kNextLink,
&ChangeList::next_link_,
GetGURLFromString);
@@ -646,23 +631,14 @@ bool ChangeList::Parse(const base::Value& value) {
////////////////////////////////////////////////////////////////////////////////
// FileLabels implementation
-FileLabels::FileLabels()
- : starred_(false),
- hidden_(false),
- trashed_(false),
- restricted_(false),
- viewed_(false) {}
+FileLabels::FileLabels() : trashed_(false) {}
FileLabels::~FileLabels() {}
// static
void FileLabels::RegisterJSONConverter(
base::JSONValueConverter<FileLabels>* converter) {
- converter->RegisterBoolField(kLabelStarred, &FileLabels::starred_);
- converter->RegisterBoolField(kLabelHidden, &FileLabels::hidden_);
converter->RegisterBoolField(kLabelTrashed, &FileLabels::trashed_);
- converter->RegisterBoolField(kLabelRestricted, &FileLabels::restricted_);
- converter->RegisterBoolField(kLabelViewed, &FileLabels::viewed_);
}
// static
@@ -717,7 +693,6 @@ scoped_ptr<ImageMediaMetadata> ImageMediaMetadata::CreateFrom(
}
bool ImageMediaMetadata::Parse(const base::Value& value) {
- return true;
base::JSONValueConverter<ImageMediaMetadata> converter;
if (!converter.Convert(value, this)) {
LOG(ERROR) << "Unable to parse: Invalid ImageMediaMetadata.";
diff --git a/chromium/google_apis/drive/drive_api_parser.h b/chromium/google_apis/drive/drive_api_parser.h
index ef02035d595..babb4cdc5c1 100644
--- a/chromium/google_apis/drive/drive_api_parser.h
+++ b/chromium/google_apis/drive/drive_api_parser.h
@@ -168,21 +168,17 @@ class AppResource {
// If empty, application name is used instead.
const std::string& object_type() const { return object_type_; }
+ // Returns the product ID.
+ const std::string& product_id() const { return product_id_; }
+
// Returns whether this application supports creating new objects.
bool supports_create() const { return supports_create_; }
- // Returns whether this application supports importing Google Docs.
- bool supports_import() const { return supports_import_; }
-
- // Returns whether this application is installed.
- bool is_installed() const { return installed_; }
-
- // Returns whether this application is authorized to access data on the
- // user's Drive.
- bool is_authorized() const { return authorized_; }
+ // Returns whether this application is removable by apps.delete API.
+ bool is_removable() const { return removable_; }
- // Returns the product URL, e.g. at Chrome Web Store.
- const GURL& product_url() const { return product_url_; }
+ // Returns the create URL, i.e., the URL for opening a new file by the app.
+ const GURL& create_url() const { return create_url_; }
// List of primary mime types supported by this WebApp. Primary status should
// trigger this WebApp becoming the default handler of file instances that
@@ -226,17 +222,11 @@ class AppResource {
void set_object_type(const std::string& object_type) {
object_type_ = object_type;
}
+ void set_product_id(const std::string& id) { product_id_ = id; }
void set_supports_create(bool supports_create) {
supports_create_ = supports_create;
}
- void set_supports_import(bool supports_import) {
- supports_import_ = supports_import;
- }
- void set_installed(bool installed) { installed_ = installed; }
- void set_authorized(bool authorized) { authorized_ = authorized; }
- void set_product_url(const GURL& product_url) {
- product_url_ = product_url;
- }
+ void set_removable(bool removable) { removable_ = removable; }
void set_primary_mimetypes(
ScopedVector<std::string> primary_mimetypes) {
primary_mimetypes_ = primary_mimetypes.Pass();
@@ -256,6 +246,9 @@ class AppResource {
void set_icons(ScopedVector<DriveAppIcon> icons) {
icons_ = icons.Pass();
}
+ void set_create_url(const GURL& url) {
+ create_url_ = url;
+ }
private:
friend class base::internal::RepeatedMessageConverter<AppResource>;
@@ -268,11 +261,10 @@ class AppResource {
std::string application_id_;
std::string name_;
std::string object_type_;
+ std::string product_id_;
bool supports_create_;
- bool supports_import_;
- bool installed_;
- bool authorized_;
- GURL product_url_;
+ bool removable_;
+ GURL create_url_;
ScopedVector<std::string> primary_mimetypes_;
ScopedVector<std::string> secondary_mimetypes_;
ScopedVector<std::string> primary_file_extensions_;
@@ -345,27 +337,18 @@ class ParentReference {
// Returns the URL for the parent in Drive.
const GURL& parent_link() const { return parent_link_; }
- // Returns true if the reference is root directory.
- bool is_root() const { return is_root_; }
-
void set_file_id(const std::string& file_id) { file_id_ = file_id; }
void set_parent_link(const GURL& parent_link) {
parent_link_ = parent_link;
}
- void set_is_root(bool is_root) { is_root_ = is_root; }
private:
- friend class base::internal::RepeatedMessageConverter<ParentReference>;
-
// Parses and initializes data members from content of |value|.
// Return false if parsing fails.
bool Parse(const base::Value& value);
std::string file_id_;
GURL parent_link_;
- bool is_root_;
-
- DISALLOW_COPY_AND_ASSIGN(ParentReference);
};
// FileLabels represents labels for file or folder.
@@ -383,22 +366,10 @@ class FileLabels {
// Creates about resource from parsed JSON.
static scoped_ptr<FileLabels> CreateFrom(const base::Value& value);
- // Whether this file is starred by the user.
- bool is_starred() const { return starred_; }
- // Whether this file is hidden from the user.
- bool is_hidden() const { return hidden_; }
// Whether this file has been trashed.
bool is_trashed() const { return trashed_; }
- // Whether viewers are prevented from downloading this file.
- bool is_restricted() const { return restricted_; }
- // Whether this file has been viewed by this user.
- bool is_viewed() const { return viewed_; }
- void set_starred(bool starred) { starred_ = starred; }
- void set_hidden(bool hidden) { hidden_ = hidden; }
void set_trashed(bool trashed) { trashed_ = trashed; }
- void set_restricted(bool restricted) { restricted_ = restricted; }
- void set_viewed(bool viewed) { viewed_ = viewed; }
private:
friend class FileResource;
@@ -407,13 +378,7 @@ class FileLabels {
// Return false if parsing fails.
bool Parse(const base::Value& value);
- bool starred_;
- bool hidden_;
bool trashed_;
- bool restricted_;
- bool viewed_;
-
- DISALLOW_COPY_AND_ASSIGN(FileLabels);
};
// ImageMediaMetadata represents image metadata for a file.
@@ -452,8 +417,6 @@ class ImageMediaMetadata {
int width_;
int height_;
int rotation_;
-
- DISALLOW_COPY_AND_ASSIGN(ImageMediaMetadata);
};
@@ -489,9 +452,6 @@ class FileResource {
// Returns ETag for this file.
const std::string& etag() const { return etag_; }
- // Returns the link to JSON of this file itself.
- const GURL& self_link() const { return self_link_; }
-
// Returns the title of this file.
const std::string& title() const { return title_; }
@@ -512,9 +472,6 @@ class FileResource {
// Returns modified time of this file.
const base::Time& modified_date() const { return modified_date_; }
- // Returns modification time by the user.
- const base::Time& modified_by_me_date() const { return modified_by_me_date_; }
-
// Returns last access time by the user.
const base::Time& last_viewed_by_me_date() const {
return last_viewed_by_me_date_;
@@ -528,13 +485,6 @@ class FileResource {
// Returns the 'shared' attribute of the file.
bool shared() const { return shared_; }
- // Returns the short-lived download URL for the file. This field exists
- // only when the file content is stored in Drive.
- const GURL& download_url() const { return download_url_; }
-
- // Returns the extension part of the filename.
- const std::string& file_extension() const { return file_extension_; }
-
// Returns MD5 checksum of this file.
const std::string& md5_checksum() const { return md5_checksum_; }
@@ -545,18 +495,8 @@ class FileResource {
// E.g. Google Document, Google Spreadsheet.
const GURL& alternate_link() const { return alternate_link_; }
- // Returns the link for embedding the file.
- const GURL& embed_link() const { return embed_link_; }
-
// Returns parent references (directories) of this file.
- const ScopedVector<ParentReference>& parents() const { return parents_; }
-
- // Returns the link to the file's thumbnail.
- const GURL& thumbnail_link() const { return thumbnail_link_; }
-
- // Returns the link to open its downloadable content, using cookie based
- // authentication.
- const GURL& web_content_link() const { return web_content_link_; }
+ const std::vector<ParentReference>& parents() const { return parents_; }
// Returns the list of links to open the resource with a web app.
const std::vector<OpenWithLink>& open_with_links() const {
@@ -569,9 +509,6 @@ class FileResource {
void set_etag(const std::string& etag) {
etag_ = etag;
}
- void set_self_link(const GURL& self_link) {
- self_link_ = self_link;
- }
void set_title(const std::string& title) {
title_ = title;
}
@@ -590,9 +527,6 @@ class FileResource {
void set_modified_date(const base::Time& modified_date) {
modified_date_ = modified_date;
}
- void set_modified_by_me_date(const base::Time& modified_by_me_date) {
- modified_by_me_date_ = modified_by_me_date;
- }
void set_last_viewed_by_me_date(const base::Time& last_viewed_by_me_date) {
last_viewed_by_me_date_ = last_viewed_by_me_date;
}
@@ -602,12 +536,6 @@ class FileResource {
void set_shared(bool shared) {
shared_ = shared;
}
- void set_download_url(const GURL& download_url) {
- download_url_ = download_url;
- }
- void set_file_extension(const std::string& file_extension) {
- file_extension_ = file_extension;
- }
void set_md5_checksum(const std::string& md5_checksum) {
md5_checksum_ = md5_checksum;
}
@@ -617,17 +545,9 @@ class FileResource {
void set_alternate_link(const GURL& alternate_link) {
alternate_link_ = alternate_link;
}
- void set_embed_link(const GURL& embed_link) {
- embed_link_ = embed_link;
- }
- void set_parents(ScopedVector<ParentReference> parents) {
- parents_ = parents.Pass();
- }
- void set_thumbnail_link(const GURL& thumbnail_link) {
- thumbnail_link_ = thumbnail_link;
- }
- void set_web_content_link(const GURL& web_content_link) {
- web_content_link_ = web_content_link;
+ std::vector<ParentReference>* mutable_parents() { return &parents_; }
+ std::vector<OpenWithLink>* mutable_open_with_links() {
+ return &open_with_links_;
}
private:
@@ -641,29 +561,20 @@ class FileResource {
std::string file_id_;
std::string etag_;
- GURL self_link_;
std::string title_;
std::string mime_type_;
FileLabels labels_;
ImageMediaMetadata image_media_metadata_;
base::Time created_date_;
base::Time modified_date_;
- base::Time modified_by_me_date_;
base::Time last_viewed_by_me_date_;
base::Time shared_with_me_date_;
bool shared_;
- GURL download_url_;
- std::string file_extension_;
std::string md5_checksum_;
int64 file_size_;
GURL alternate_link_;
- GURL embed_link_;
- ScopedVector<ParentReference> parents_;
- GURL thumbnail_link_;
- GURL web_content_link_;
+ std::vector<ParentReference> parents_;
std::vector<OpenWithLink> open_with_links_;
-
- DISALLOW_COPY_AND_ASSIGN(FileResource);
};
// FileList represents a collection of files and folders.
@@ -684,32 +595,17 @@ class FileList {
// Creates file list from parsed JSON.
static scoped_ptr<FileList> CreateFrom(const base::Value& value);
- // Returns the ETag of the list.
- const std::string& etag() const { return etag_; }
-
- // Returns the page token for the next page of files, if the list is large
- // to fit in one response. If this is empty, there is no more file lists.
- const std::string& next_page_token() const { return next_page_token_; }
-
// Returns a link to the next page of files. The URL includes the next page
// token.
const GURL& next_link() const { return next_link_; }
// Returns a set of files in this list.
const ScopedVector<FileResource>& items() const { return items_; }
+ ScopedVector<FileResource>* mutable_items() { return &items_; }
- void set_etag(const std::string& etag) {
- etag_ = etag;
- }
- void set_next_page_token(const std::string& next_page_token) {
- next_page_token_ = next_page_token;
- }
void set_next_link(const GURL& next_link) {
next_link_ = next_link;
}
- void set_items(ScopedVector<FileResource> items) {
- items_ = items.Pass();
- }
private:
friend class DriveAPIParserTest;
@@ -719,8 +615,6 @@ class FileList {
// Return false if parsing fails.
bool Parse(const base::Value& value);
- std::string etag_;
- std::string next_page_token_;
GURL next_link_;
ScopedVector<FileResource> items_;
@@ -754,6 +648,10 @@ class ChangeResource {
// Returns FileResource of the file which the change refers to.
const FileResource* file() const { return file_.get(); }
+ FileResource* mutable_file() { return file_.get(); }
+
+ // Returns the time of this modification.
+ const base::Time& modification_date() const { return modification_date_; }
void set_change_id(int64 change_id) {
change_id_ = change_id;
@@ -767,6 +665,9 @@ class ChangeResource {
void set_file(scoped_ptr<FileResource> file) {
file_ = file.Pass();
}
+ void set_modification_date(const base::Time& modification_date) {
+ modification_date_ = modification_date;
+ }
private:
friend class base::internal::RepeatedMessageConverter<ChangeResource>;
@@ -780,6 +681,7 @@ class ChangeResource {
std::string file_id_;
bool deleted_;
scoped_ptr<FileResource> file_;
+ base::Time modification_date_;
DISALLOW_COPY_AND_ASSIGN(ChangeResource);
};
@@ -802,13 +704,6 @@ class ChangeList {
// Creates change list from parsed JSON.
static scoped_ptr<ChangeList> CreateFrom(const base::Value& value);
- // Returns the ETag of the list.
- const std::string& etag() const { return etag_; }
-
- // Returns the page token for the next page of files, if the list is large
- // to fit in one response. If this is empty, there is no more file lists.
- const std::string& next_page_token() const { return next_page_token_; }
-
// Returns a link to the next page of files. The URL includes the next page
// token.
const GURL& next_link() const { return next_link_; }
@@ -818,22 +713,14 @@ class ChangeList {
// Returns a set of changes in this list.
const ScopedVector<ChangeResource>& items() const { return items_; }
+ ScopedVector<ChangeResource>* mutable_items() { return &items_; }
- void set_etag(const std::string& etag) {
- etag_ = etag;
- }
- void set_next_page_token(const std::string& next_page_token) {
- next_page_token_ = next_page_token;
- }
void set_next_link(const GURL& next_link) {
next_link_ = next_link;
}
void set_largest_change_id(int64 largest_change_id) {
largest_change_id_ = largest_change_id;
}
- void set_items(ScopedVector<ChangeResource> items) {
- items_ = items.Pass();
- }
private:
friend class DriveAPIParserTest;
@@ -843,8 +730,6 @@ class ChangeList {
// Return false if parsing fails.
bool Parse(const base::Value& value);
- std::string etag_;
- std::string next_page_token_;
GURL next_link_;
int64 largest_change_id_;
ScopedVector<ChangeResource> items_;
diff --git a/chromium/google_apis/drive/drive_api_parser_unittest.cc b/chromium/google_apis/drive/drive_api_parser_unittest.cc
index 0960b275bf5..6f9ee65e109 100644
--- a/chromium/google_apis/drive/drive_api_parser_unittest.cc
+++ b/chromium/google_apis/drive/drive_api_parser_unittest.cc
@@ -50,12 +50,8 @@ TEST(DriveAPIParserTest, AppListParser) {
EXPECT_EQ("Drive app 1", app1.name());
EXPECT_EQ("", app1.object_type());
EXPECT_TRUE(app1.supports_create());
- EXPECT_TRUE(app1.supports_import());
- EXPECT_TRUE(app1.is_installed());
- EXPECT_FALSE(app1.is_authorized());
- EXPECT_EQ("https://chrome.google.com/webstore/detail/"
- "abcdefghabcdefghabcdefghabcdefgh",
- app1.product_url().spec());
+ EXPECT_TRUE(app1.is_removable());
+ EXPECT_EQ("abcdefghabcdefghabcdefghabcdefgh", app1.product_id());
ASSERT_EQ(1U, app1.primary_mimetypes().size());
EXPECT_EQ("application/vnd.google-apps.drive-sdk.123456788192",
@@ -82,18 +78,16 @@ TEST(DriveAPIParserTest, AppListParser) {
EXPECT_EQ(16, icon6.icon_side_length());
EXPECT_EQ("http://www.example.com/ds16.png", icon6.icon_url().spec());
+ EXPECT_EQ("https://www.example.com/createForApp1", app1.create_url().spec());
+
// Check Drive app 2
const AppResource& app2 = *applist->items()[1];
EXPECT_EQ("876543210000", app2.application_id());
EXPECT_EQ("Drive app 2", app2.name());
EXPECT_EQ("", app2.object_type());
EXPECT_FALSE(app2.supports_create());
- EXPECT_FALSE(app2.supports_import());
- EXPECT_TRUE(app2.is_installed());
- EXPECT_FALSE(app2.is_authorized());
- EXPECT_EQ("https://chrome.google.com/webstore/detail/"
- "hgfedcbahgfedcbahgfedcbahgfedcba",
- app2.product_url().spec());
+ EXPECT_FALSE(app2.is_removable());
+ EXPECT_EQ("hgfedcbahgfedcbahgfedcbahgfedcba", app2.product_id());
ASSERT_EQ(3U, app2.primary_mimetypes().size());
EXPECT_EQ("image/jpeg", *app2.primary_mimetypes()[0]);
@@ -110,6 +104,8 @@ TEST(DriveAPIParserTest, AppListParser) {
EXPECT_EQ(DriveAppIcon::DOCUMENT, icon2.category());
EXPECT_EQ(10, icon2.icon_side_length());
EXPECT_EQ("http://www.example.com/d10.png", icon2.icon_url().spec());
+
+ EXPECT_EQ("https://www.example.com/createForApp2", app2.create_url().spec());
}
// Test file list parsing.
@@ -123,12 +119,6 @@ TEST(DriveAPIParserTest, FileListParser) {
scoped_ptr<FileList> filelist(new FileList);
EXPECT_TRUE(filelist->Parse(*document));
- EXPECT_EQ("\"WtRjAPZWbDA7_fkFjc5ojsEvDEF/zyHTfoHpnRHovyi8bWpwK0DXABC\"",
- filelist->etag());
- EXPECT_EQ("EAIaggELEgA6egpi96It9mH_____f_8AAP__AAD_okhU-cHLz83KzszMxsjMzs_Ry"
- "NGJnridyrbHs7u9tv8AAP__AP7__n__AP8AokhU-cHLz83KzszMxsjMzs_RyNGJnr"
- "idyrbHs7u9tv8A__4QZCEiXPTi_wtIgTkAAAAAngnSXUgCDEAAIgsJPgart10AAAA"
- "ABC", filelist->next_page_token());
EXPECT_EQ(GURL("https://www.googleapis.com/drive/v2/files?pageToken=EAIaggEL"
"EgA6egpi96It9mH_____f_8AAP__AAD_okhU-cHLz83KzszMxsjMzs_RyNGJ"
"nridyrbHs7u9tv8AAP__AP7__n__AP8AokhU-cHLz83KzszMxsjMzs_RyNGJ"
@@ -144,11 +134,7 @@ TEST(DriveAPIParserTest, FileListParser) {
EXPECT_EQ("My first file data", file1.title());
EXPECT_EQ("application/octet-stream", file1.mime_type());
- EXPECT_FALSE(file1.labels().is_starred());
- EXPECT_FALSE(file1.labels().is_hidden());
EXPECT_FALSE(file1.labels().is_trashed());
- EXPECT_FALSE(file1.labels().is_restricted());
- EXPECT_TRUE(file1.labels().is_viewed());
EXPECT_FALSE(file1.shared());
EXPECT_EQ(640, file1.image_media_metadata().width());
@@ -164,29 +150,19 @@ TEST(DriveAPIParserTest, FileListParser) {
ASSERT_TRUE(
util::GetTimeFromString("2012-07-27T05:43:20.269Z", &modified_time));
EXPECT_EQ(modified_time, file1.modified_date());
- EXPECT_EQ(modified_time, file1.modified_by_me_date());
ASSERT_EQ(1U, file1.parents().size());
- EXPECT_EQ("0B4v7G8yEYAWHYW1OcExsUVZLABC", file1.parents()[0]->file_id());
+ EXPECT_EQ("0B4v7G8yEYAWHYW1OcExsUVZLABC", file1.parents()[0].file_id());
EXPECT_EQ(GURL("https://www.googleapis.com/drive/v2/files/"
"0B4v7G8yEYAWHYW1OcExsUVZLABC"),
- file1.parents()[0]->parent_link());
- EXPECT_FALSE(file1.parents()[0]->is_root());
+ file1.parents()[0].parent_link());
- EXPECT_EQ(GURL("https://www.example.com/download"), file1.download_url());
- EXPECT_EQ("ext", file1.file_extension());
EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", file1.md5_checksum());
EXPECT_EQ(1000U, file1.file_size());
- EXPECT_EQ(GURL("https://www.googleapis.com/drive/v2/files/"
- "0B4v7G8yEYAWHUmRrU2lMS2hLABC"),
- file1.self_link());
EXPECT_EQ(GURL("https://docs.google.com/file/d/"
"0B4v7G8yEYAWHUmRrU2lMS2hLABC/edit"),
file1.alternate_link());
- EXPECT_EQ(GURL("https://docs.google.com/uc?"
- "id=0B4v7G8yEYAWHUmRrU2lMS2hLABC&export=download"),
- file1.web_content_link());
ASSERT_EQ(1U, file1.open_with_links().size());
EXPECT_EQ("1234567890", file1.open_with_links()[0].app_id);
EXPECT_EQ(GURL("http://open_with_link/url"),
@@ -197,11 +173,7 @@ TEST(DriveAPIParserTest, FileListParser) {
EXPECT_EQ("Test Google Document", file2.title());
EXPECT_EQ("application/vnd.google-apps.document", file2.mime_type());
- EXPECT_TRUE(file2.labels().is_starred());
- EXPECT_TRUE(file2.labels().is_hidden());
EXPECT_TRUE(file2.labels().is_trashed());
- EXPECT_TRUE(file2.labels().is_restricted());
- EXPECT_TRUE(file2.labels().is_viewed());
EXPECT_TRUE(file2.shared());
EXPECT_EQ(-1, file2.image_media_metadata().width());
@@ -217,13 +189,6 @@ TEST(DriveAPIParserTest, FileListParser) {
ASSERT_EQ(0U, file2.parents().size());
- EXPECT_EQ(GURL("https://docs.google.com/a/chromium.org/document/d/"
- "1Pc8jzfU1ErbN_eucMMqdqzY3eBm0v8sxXm_1CtLxABC/preview"),
- file2.embed_link());
- EXPECT_EQ(GURL("https://docs.google.com/feeds/vt?gd=true&"
- "id=1Pc8jzfU1ErbN_eucMMqdqzY3eBm0v8sxXm_1CtLxABC&"
- "v=3&s=AMedNnoAAAAAUBJyB0g8HbxZaLRnlztxefZPS24LiXYZ&sz=s220"),
- file2.thumbnail_link());
EXPECT_EQ(0U, file2.open_with_links().size());
// Check file 3 (a folder)
@@ -235,8 +200,7 @@ TEST(DriveAPIParserTest, FileListParser) {
EXPECT_FALSE(file3.shared());
ASSERT_EQ(1U, file3.parents().size());
- EXPECT_EQ("0AIv7G8yEYAWHUk9ABC", file3.parents()[0]->file_id());
- EXPECT_TRUE(file3.parents()[0]->is_root());
+ EXPECT_EQ("0AIv7G8yEYAWHUk9ABC", file3.parents()[0].file_id());
EXPECT_EQ(0U, file3.open_with_links().size());
}
@@ -251,9 +215,6 @@ TEST(DriveAPIParserTest, ChangeListParser) {
scoped_ptr<ChangeList> changelist(new ChangeList);
EXPECT_TRUE(changelist->Parse(*document));
- EXPECT_EQ("\"Lp2bjAtLP341hvGmYHhxjYyBPJ8/BWbu_eylt5f_aGtCN6mGRv9hABC\"",
- changelist->etag());
- EXPECT_EQ("8929", changelist->next_page_token());
EXPECT_EQ("https://www.googleapis.com/drive/v2/changes?pageToken=8929",
changelist->next_link().spec());
EXPECT_EQ(13664, changelist->largest_change_id());
@@ -266,6 +227,7 @@ TEST(DriveAPIParserTest, ChangeListParser) {
EXPECT_EQ("1Pc8jzfU1ErbN_eucMMqdqzY3eBm0v8sxXm_1CtLxABC", change1.file_id());
EXPECT_EQ(change1.file_id(), change1.file()->file_id());
EXPECT_FALSE(change1.file()->shared());
+ EXPECT_EQ(change1.file()->modified_date(), change1.modification_date());
const ChangeResource& change2 = *changelist->items()[1];
EXPECT_EQ(8424, change2.change_id());
@@ -273,6 +235,7 @@ TEST(DriveAPIParserTest, ChangeListParser) {
EXPECT_EQ("0B4v7G8yEYAWHUmRrU2lMS2hLABC", change2.file_id());
EXPECT_EQ(change2.file_id(), change2.file()->file_id());
EXPECT_TRUE(change2.file()->shared());
+ EXPECT_EQ(change2.file()->modified_date(), change2.modification_date());
const ChangeResource& change3 = *changelist->items()[2];
EXPECT_EQ(8429, change3.change_id());
@@ -280,12 +243,17 @@ TEST(DriveAPIParserTest, ChangeListParser) {
EXPECT_EQ("0B4v7G8yEYAWHYW1OcExsUVZLABC", change3.file_id());
EXPECT_EQ(change3.file_id(), change3.file()->file_id());
EXPECT_FALSE(change3.file()->shared());
+ EXPECT_EQ(change3.file()->modified_date(), change3.modification_date());
// Deleted entry.
const ChangeResource& change4 = *changelist->items()[3];
EXPECT_EQ(8430, change4.change_id());
EXPECT_EQ("ABCv7G8yEYAWHc3Y5X0hMSkJYXYZ", change4.file_id());
EXPECT_TRUE(change4.is_deleted());
+ base::Time modification_time;
+ ASSERT_TRUE(util::GetTimeFromString("2012-07-27T12:34:56.789Z",
+ &modification_time));
+ EXPECT_EQ(modification_time, change4.modification_date());
}
TEST(DriveAPIParserTest, HasKind) {
diff --git a/chromium/google_apis/drive/drive_api_requests.cc b/chromium/google_apis/drive/drive_api_requests.cc
index f0960e6f237..a63757112b8 100644
--- a/chromium/google_apis/drive/drive_api_requests.cc
+++ b/chromium/google_apis/drive/drive_api_requests.cc
@@ -113,6 +113,15 @@ void ParseFileResourceWithUploadRangeAndRun(
callback.Run(response, file_resource.Pass());
}
+// Creates a Parents value which can be used as a part of request body.
+scoped_ptr<base::DictionaryValue> CreateParentValue(
+ const std::string& file_id) {
+ scoped_ptr<base::DictionaryValue> parent(new base::DictionaryValue);
+ parent->SetString("kind", kParentLinkKind);
+ parent->SetString("id", file_id);
+ return parent.Pass();
+}
+
} // namespace
namespace drive {
@@ -153,6 +162,29 @@ GURL FilesGetRequest::GetURLInternal() const {
return url_generator_.GetFilesGetUrl(file_id_);
}
+//============================ FilesAuthorizeRequest ===========================
+
+FilesAuthorizeRequest::FilesAuthorizeRequest(
+ RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const FileResourceCallback& callback)
+ : DriveApiDataRequest(
+ sender,
+ base::Bind(&ParseJsonAndRun<FileResource>, callback)),
+ url_generator_(url_generator) {
+ DCHECK(!callback.is_null());
+}
+
+FilesAuthorizeRequest::~FilesAuthorizeRequest() {}
+
+net::URLFetcher::RequestType FilesAuthorizeRequest::GetRequestType() const {
+ return net::URLFetcher::POST;
+}
+
+GURL FilesAuthorizeRequest::GetURLInternal() const {
+ return url_generator_.GetFilesAuthorizeUrl(file_id_, app_id_);
+}
+
//============================ FilesInsertRequest ============================
FilesInsertRequest::FilesInsertRequest(
@@ -178,9 +210,17 @@ bool FilesInsertRequest::GetContentData(std::string* upload_content_type,
base::DictionaryValue root;
+ if (!last_viewed_by_me_date_.is_null()) {
+ root.SetString("lastViewedByMeDate",
+ util::FormatTimeAsString(last_viewed_by_me_date_));
+ }
+
if (!mime_type_.empty())
root.SetString("mimeType", mime_type_);
+ if (!modified_date_.is_null())
+ root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
+
if (!parents_.empty()) {
base::ListValue* parents_value = new base::ListValue;
for (size_t i = 0; i < parents_.size(); ++i) {
@@ -491,18 +531,40 @@ GURL ChangesListNextPageRequest::GetURLInternal() const {
AppsListRequest::AppsListRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
+ bool use_internal_endpoint,
const AppListCallback& callback)
: DriveApiDataRequest(
sender,
base::Bind(&ParseJsonAndRun<AppList>, callback)),
- url_generator_(url_generator) {
+ url_generator_(url_generator),
+ use_internal_endpoint_(use_internal_endpoint) {
DCHECK(!callback.is_null());
}
AppsListRequest::~AppsListRequest() {}
GURL AppsListRequest::GetURLInternal() const {
- return url_generator_.GetAppsListUrl();
+ return url_generator_.GetAppsListUrl(use_internal_endpoint_);
+}
+
+//============================== AppsDeleteRequest ===========================
+
+AppsDeleteRequest::AppsDeleteRequest(RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const EntryActionCallback& callback)
+ : EntryActionRequest(sender, callback),
+ url_generator_(url_generator) {
+ DCHECK(!callback.is_null());
+}
+
+AppsDeleteRequest::~AppsDeleteRequest() {}
+
+net::URLFetcher::RequestType AppsDeleteRequest::GetRequestType() const {
+ return net::URLFetcher::DELETE_REQUEST;
+}
+
+GURL AppsDeleteRequest::GetURL() const {
+ return url_generator_.GetAppsDeleteUrl(app_id_);
}
//========================== ChildrenInsertRequest ============================
@@ -582,7 +644,7 @@ InitiateUploadNewFileRequest::InitiateUploadNewFileRequest(
InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() {}
GURL InitiateUploadNewFileRequest::GetURL() const {
- return url_generator_.GetInitiateUploadNewFileUrl();
+ return url_generator_.GetInitiateUploadNewFileUrl(!modified_date_.is_null());
}
net::URLFetcher::RequestType
@@ -599,15 +661,16 @@ bool InitiateUploadNewFileRequest::GetContentData(
root.SetString("title", title_);
// Fill parent link.
- {
- scoped_ptr<base::DictionaryValue> parent(new base::DictionaryValue);
- parent->SetString("kind", kParentLinkKind);
- parent->SetString("id", parent_resource_id_);
+ scoped_ptr<base::ListValue> parents(new base::ListValue);
+ parents->Append(CreateParentValue(parent_resource_id_).release());
+ root.Set("parents", parents.release());
- scoped_ptr<base::ListValue> parents(new base::ListValue);
- parents->Append(parent.release());
+ if (!modified_date_.is_null())
+ root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
- root.Set("parents", parents.release());
+ if (!last_viewed_by_me_date_.is_null()) {
+ root.SetString("lastViewedByMeDate",
+ util::FormatTimeAsString(last_viewed_by_me_date_));
}
base::JSONWriter::Write(&root, upload_content);
@@ -639,7 +702,8 @@ InitiateUploadExistingFileRequest::InitiateUploadExistingFileRequest(
InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() {}
GURL InitiateUploadExistingFileRequest::GetURL() const {
- return url_generator_.GetInitiateUploadExistingFileUrl(resource_id_);
+ return url_generator_.GetInitiateUploadExistingFileUrl(
+ resource_id_, !modified_date_.is_null());
}
net::URLFetcher::RequestType
@@ -655,6 +719,37 @@ InitiateUploadExistingFileRequest::GetExtraRequestHeaders() const {
return headers;
}
+bool InitiateUploadExistingFileRequest::GetContentData(
+ std::string* upload_content_type,
+ std::string* upload_content) {
+ base::DictionaryValue root;
+ if (!parent_resource_id_.empty()) {
+ scoped_ptr<base::ListValue> parents(new base::ListValue);
+ parents->Append(CreateParentValue(parent_resource_id_).release());
+ root.Set("parents", parents.release());
+ }
+
+ if (!title_.empty())
+ root.SetString("title", title_);
+
+ if (!modified_date_.is_null())
+ root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
+
+ if (!last_viewed_by_me_date_.is_null()) {
+ root.SetString("lastViewedByMeDate",
+ util::FormatTimeAsString(last_viewed_by_me_date_));
+ }
+
+ if (root.empty())
+ return false;
+
+ *upload_content_type = kContentTypeApplicationJson;
+ base::JSONWriter::Write(&root, upload_content);
+ DVLOG(1) << "InitiateUploadExistingFile data: " << *upload_content_type
+ << ", [" << *upload_content << "]";
+ return true;
+}
+
//============================ ResumeUploadRequest ===========================
ResumeUploadRequest::ResumeUploadRequest(
@@ -739,5 +834,72 @@ DownloadFileRequest::DownloadFileRequest(
DownloadFileRequest::~DownloadFileRequest() {
}
+//========================== PermissionsInsertRequest ==========================
+
+PermissionsInsertRequest::PermissionsInsertRequest(
+ RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const EntryActionCallback& callback)
+ : EntryActionRequest(sender, callback),
+ url_generator_(url_generator),
+ type_(PERMISSION_TYPE_USER),
+ role_(PERMISSION_ROLE_READER) {
+}
+
+PermissionsInsertRequest::~PermissionsInsertRequest() {
+}
+
+GURL PermissionsInsertRequest::GetURL() const {
+ return url_generator_.GetPermissionsInsertUrl(id_);
+}
+
+net::URLFetcher::RequestType
+PermissionsInsertRequest::GetRequestType() const {
+ return net::URLFetcher::POST;
+}
+
+bool PermissionsInsertRequest::GetContentData(std::string* upload_content_type,
+ std::string* upload_content) {
+ *upload_content_type = kContentTypeApplicationJson;
+
+ base::DictionaryValue root;
+ switch (type_) {
+ case PERMISSION_TYPE_ANYONE:
+ root.SetString("type", "anyone");
+ break;
+ case PERMISSION_TYPE_DOMAIN:
+ root.SetString("type", "domain");
+ break;
+ case PERMISSION_TYPE_GROUP:
+ root.SetString("type", "group");
+ break;
+ case PERMISSION_TYPE_USER:
+ root.SetString("type", "user");
+ break;
+ }
+ switch (role_) {
+ case PERMISSION_ROLE_OWNER:
+ root.SetString("role", "owner");
+ break;
+ case PERMISSION_ROLE_READER:
+ root.SetString("role", "reader");
+ break;
+ case PERMISSION_ROLE_WRITER:
+ root.SetString("role", "writer");
+ break;
+ case PERMISSION_ROLE_COMMENTER:
+ root.SetString("role", "reader");
+ {
+ base::ListValue* list = new base::ListValue;
+ list->AppendString("commenter");
+ root.Set("additionalRoles", list);
+ }
+ break;
+ }
+ root.SetString("value", value_);
+ base::JSONWriter::Write(&root, upload_content);
+ return true;
+}
+
} // namespace drive
} // namespace google_apis
diff --git a/chromium/google_apis/drive/drive_api_requests.h b/chromium/google_apis/drive/drive_api_requests.h
index 6f10aa66d95..59bffd67b87 100644
--- a/chromium/google_apis/drive/drive_api_requests.h
+++ b/chromium/google_apis/drive/drive_api_requests.h
@@ -93,6 +93,38 @@ class FilesGetRequest : public DriveApiDataRequest {
DISALLOW_COPY_AND_ASSIGN(FilesGetRequest);
};
+//============================ FilesAuthorizeRequest ===========================
+
+// This class performs request for authorizing an app to access a file.
+// This request is mapped to /drive/v2internal/file/authorize internal endpoint.
+class FilesAuthorizeRequest : public DriveApiDataRequest {
+ public:
+ FilesAuthorizeRequest(RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const FileResourceCallback& callback);
+ virtual ~FilesAuthorizeRequest();
+
+ // Required parameter.
+ const std::string& file_id() const { return file_id_; }
+ void set_file_id(const std::string& file_id) { file_id_ = file_id; }
+ const std::string& app_id() const { return app_id_; }
+ void set_app_id(const std::string& app_id) { app_id_ = app_id; }
+
+ protected:
+ // Overridden from GetDataRequest.
+ virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
+
+ // Overridden from DriveApiDataRequest.
+ virtual GURL GetURLInternal() const OVERRIDE;
+
+ private:
+ const DriveApiUrlGenerator url_generator_;
+ std::string file_id_;
+ std::string app_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilesAuthorizeRequest);
+};
+
//============================ FilesInsertRequest =============================
// This class performs the request for creating a resource.
@@ -108,11 +140,23 @@ class FilesInsertRequest : public DriveApiDataRequest {
virtual ~FilesInsertRequest();
// Optional request body.
+ const base::Time& last_viewed_by_me_date() const {
+ return last_viewed_by_me_date_;
+ }
+ void set_last_viewed_by_me_date(const base::Time& last_viewed_by_me_date) {
+ last_viewed_by_me_date_ = last_viewed_by_me_date;
+ }
+
const std::string& mime_type() const { return mime_type_; }
void set_mime_type(const std::string& mime_type) {
mime_type_ = mime_type;
}
+ const base::Time& modified_date() const { return modified_date_; }
+ void set_modified_date(const base::Time& modified_date) {
+ modified_date_ = modified_date;
+ }
+
const std::vector<std::string>& parents() const { return parents_; }
void add_parent(const std::string& parent) { parents_.push_back(parent); }
@@ -131,7 +175,9 @@ class FilesInsertRequest : public DriveApiDataRequest {
private:
const DriveApiUrlGenerator url_generator_;
+ base::Time last_viewed_by_me_date_;
std::string mime_type_;
+ base::Time modified_date_;
std::vector<std::string> parents_;
std::string title_;
@@ -491,6 +537,7 @@ class AppsListRequest : public DriveApiDataRequest {
public:
AppsListRequest(RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
+ bool use_internal_endpoint,
const AppListCallback& callback);
virtual ~AppsListRequest();
@@ -500,10 +547,39 @@ class AppsListRequest : public DriveApiDataRequest {
private:
const DriveApiUrlGenerator url_generator_;
+ bool use_internal_endpoint_;
DISALLOW_COPY_AND_ASSIGN(AppsListRequest);
};
+//============================= AppsDeleteRequest ==============================
+
+// This class performs the request for deleting a Drive app.
+// This request is mapped to
+// https://developers.google.com/drive/v2/reference/files/trash
+class AppsDeleteRequest : public EntryActionRequest {
+ public:
+ AppsDeleteRequest(RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const EntryActionCallback& callback);
+ virtual ~AppsDeleteRequest();
+
+ // Required parameter.
+ const std::string& app_id() const { return app_id_; }
+ void set_app_id(const std::string& app_id) { app_id_ = app_id; }
+
+ protected:
+ // Overridden from UrlFetchRequestBase.
+ virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
+ virtual GURL GetURL() const OVERRIDE;
+
+ private:
+ const DriveApiUrlGenerator url_generator_;
+ std::string app_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppsDeleteRequest);
+};
+
//========================== ChildrenInsertRequest ============================
// This class performs the request for inserting a resource to a directory.
@@ -596,6 +672,18 @@ class InitiateUploadNewFileRequest : public InitiateUploadRequestBase {
const InitiateUploadCallback& callback);
virtual ~InitiateUploadNewFileRequest();
+ // Optional parameters.
+ const base::Time& modified_date() const { return modified_date_; }
+ void set_modified_date(const base::Time& modified_date) {
+ modified_date_ = modified_date;
+ }
+ const base::Time& last_viewed_by_me_date() const {
+ return last_viewed_by_me_date_;
+ }
+ void set_last_viewed_by_me_date(const base::Time& last_viewed_by_me_date) {
+ last_viewed_by_me_date_ = last_viewed_by_me_date;
+ }
+
protected:
// UrlFetchRequestBase overrides.
virtual GURL GetURL() const OVERRIDE;
@@ -608,6 +696,9 @@ class InitiateUploadNewFileRequest : public InitiateUploadRequestBase {
const std::string parent_resource_id_;
const std::string title_;
+ base::Time modified_date_;
+ base::Time last_viewed_by_me_date_;
+
DISALLOW_COPY_AND_ASSIGN(InitiateUploadNewFileRequest);
};
@@ -631,17 +722,43 @@ class InitiateUploadExistingFileRequest : public InitiateUploadRequestBase {
const InitiateUploadCallback& callback);
virtual ~InitiateUploadExistingFileRequest();
+
+ // Optional parameters.
+ const std::string& parent_resource_id() const { return parent_resource_id_; }
+ void set_parent_resource_id(const std::string& parent_resource_id) {
+ parent_resource_id_ = parent_resource_id;
+ }
+ const std::string& title() const { return title_; }
+ void set_title(const std::string& title) { title_ = title; }
+ const base::Time& modified_date() const { return modified_date_; }
+ void set_modified_date(const base::Time& modified_date) {
+ modified_date_ = modified_date;
+ }
+ const base::Time& last_viewed_by_me_date() const {
+ return last_viewed_by_me_date_;
+ }
+ void set_last_viewed_by_me_date(const base::Time& last_viewed_by_me_date) {
+ last_viewed_by_me_date_ = last_viewed_by_me_date;
+ }
+
protected:
// UrlFetchRequestBase overrides.
virtual GURL GetURL() const OVERRIDE;
virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
+ virtual bool GetContentData(std::string* upload_content_type,
+ std::string* upload_content) OVERRIDE;
private:
const DriveApiUrlGenerator url_generator_;
const std::string resource_id_;
const std::string etag_;
+ std::string parent_resource_id_;
+ std::string title_;
+ base::Time modified_date_;
+ base::Time last_viewed_by_me_date_;
+
DISALLOW_COPY_AND_ASSIGN(InitiateUploadExistingFileRequest);
};
@@ -727,6 +844,54 @@ class DownloadFileRequest : public DownloadFileRequestBase {
DISALLOW_COPY_AND_ASSIGN(DownloadFileRequest);
};
+//========================== PermissionsInsertRequest ==========================
+
+// Enumeration type for specifying type of permissions.
+enum PermissionType {
+ PERMISSION_TYPE_ANYONE,
+ PERMISSION_TYPE_DOMAIN,
+ PERMISSION_TYPE_GROUP,
+ PERMISSION_TYPE_USER,
+};
+
+// Enumeration type for specifying the role of permissions.
+enum PermissionRole {
+ PERMISSION_ROLE_OWNER,
+ PERMISSION_ROLE_READER,
+ PERMISSION_ROLE_WRITER,
+ PERMISSION_ROLE_COMMENTER,
+};
+
+// This class performs the request for adding permission on a specified file.
+class PermissionsInsertRequest : public EntryActionRequest {
+ public:
+ // See https://developers.google.com/drive/v2/reference/permissions/insert.
+ PermissionsInsertRequest(RequestSender* sender,
+ const DriveApiUrlGenerator& url_generator,
+ const EntryActionCallback& callback);
+ virtual ~PermissionsInsertRequest();
+
+ void set_id(const std::string& id) { id_ = id; }
+ void set_type(PermissionType type) { type_ = type; }
+ void set_role(PermissionRole role) { role_ = role; }
+ void set_value(const std::string& value) { value_ = value; }
+
+ // UrlFetchRequestBase overrides.
+ virtual GURL GetURL() const OVERRIDE;
+ virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
+ virtual bool GetContentData(std::string* upload_content_type,
+ std::string* upload_content) OVERRIDE;
+
+ private:
+ const DriveApiUrlGenerator url_generator_;
+ std::string id_;
+ PermissionType type_;
+ PermissionRole role_;
+ std::string value_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermissionsInsertRequest);
+};
+
} // namespace drive
} // namespace google_apis
diff --git a/chromium/google_apis/drive/drive_api_requests_unittest.cc b/chromium/google_apis/drive/drive_api_requests_unittest.cc
index 466240cd369..9281b866a88 100644
--- a/chromium/google_apis/drive/drive_api_requests_unittest.cc
+++ b/chromium/google_apis/drive/drive_api_requests_unittest.cc
@@ -6,6 +6,7 @@
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
@@ -37,6 +38,13 @@ const char kTestChildrenResponse[] =
"\"childLink\": \"child_link\",\n"
"}\n";
+const char kTestPermissionResponse[] =
+ "{\n"
+ "\"kind\": \"drive#permission\",\n"
+ "\"id\": \"resource_id\",\n"
+ "\"selfLink\": \"self_link\",\n"
+ "}\n";
+
const char kTestUploadExistingFilePath[] = "/upload/existingfile/path";
const char kTestUploadNewFilePath[] = "/upload/newfile/path";
const char kTestDownloadPathPrefix[] = "/download/";
@@ -418,6 +426,10 @@ TEST_F(DriveApiRequestsTest, DriveApiDataRequest_Fields) {
}
TEST_F(DriveApiRequestsTest, FilesInsertRequest) {
+ const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123};
+ const base::Time::Exploded kLastViewedByMeDate =
+ {2013, 7, 0, 19, 15, 59, 13, 123};
+
// Set an expected data file containing the directory's entry data.
expected_data_file_path_ =
test_util::GetTestFilePath("drive/directory_entry.json");
@@ -434,7 +446,10 @@ TEST_F(DriveApiRequestsTest, FilesInsertRequest) {
test_util::CreateQuitCallback(
&run_loop,
test_util::CreateCopyResultCallback(&error, &file_resource)));
+ request->set_last_viewed_by_me_date(
+ base::Time::FromUTCExploded(kLastViewedByMeDate));
request->set_mime_type("application/vnd.google-apps.folder");
+ request->set_modified_date(base::Time::FromUTCExploded(kModifiedDate));
request->add_parent("root");
request->set_title("new directory");
request_sender_->StartRequestWithRetry(request);
@@ -447,6 +462,12 @@ TEST_F(DriveApiRequestsTest, FilesInsertRequest) {
EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ("{\"lastViewedByMeDate\":\"2013-07-19T15:59:13.123Z\","
+ "\"mimeType\":\"application/vnd.google-apps.folder\","
+ "\"modifiedDate\":\"2012-07-19T15:59:13.123Z\","
+ "\"parents\":[{\"id\":\"root\"}],"
+ "\"title\":\"new directory\"}",
+ http_request_.content);
scoped_ptr<FileResource> expected(
FileResource::CreateFrom(
@@ -585,6 +606,7 @@ TEST_F(DriveApiRequestsTest, AppsListRequest) {
drive::AppsListRequest* request = new drive::AppsListRequest(
request_sender_.get(),
*url_generator_,
+ false, // use_internal_endpoint
test_util::CreateQuitCallback(
&run_loop,
test_util::CreateCopyResultCallback(&error, &app_list)));
@@ -1257,6 +1279,60 @@ TEST_F(DriveApiRequestsTest, UploadNewLargeFileRequest) {
}
}
+TEST_F(DriveApiRequestsTest, UploadNewFileWithMetadataRequest) {
+ const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123};
+ const base::Time::Exploded kLastViewedByMeDate =
+ {2013, 7, 0, 19, 15, 59, 13, 123};
+
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadNewFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const std::string kTestContent(100, 'a');
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
+
+ // Initiate uploading a new file to the directory with "parent_resource_id".
+ {
+ base::RunLoop run_loop;
+ drive::InitiateUploadNewFileRequest* request =
+ new drive::InitiateUploadNewFileRequest(
+ request_sender_.get(),
+ *url_generator_,
+ kTestContentType,
+ kTestContent.size(),
+ "parent_resource_id", // The resource id of the parent directory.
+ "new file title", // The title of the file being uploaded.
+ test_util::CreateQuitCallback(
+ &run_loop,
+ test_util::CreateCopyResultCallback(&error, &upload_url)));
+ request->set_modified_date(base::Time::FromUTCExploded(kModifiedDate));
+ request->set_last_viewed_by_me_date(
+ base::Time::FromUTCExploded(kLastViewedByMeDate));
+ request_sender_->StartRequestWithRetry(request);
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(kTestUploadNewFilePath, upload_url.path());
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
+ http_request_.headers["X-Upload-Content-Length"]);
+
+ EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files?uploadType=resumable&setModifiedDate=true",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ("{\"lastViewedByMeDate\":\"2013-07-19T15:59:13.123Z\","
+ "\"modifiedDate\":\"2012-07-19T15:59:13.123Z\","
+ "\"parents\":[{\"id\":\"parent_resource_id\","
+ "\"kind\":\"drive#fileLink\"}],"
+ "\"title\":\"new file title\"}",
+ http_request_.content);
+}
+
TEST_F(DriveApiRequestsTest, UploadExistingFileRequest) {
// Set an expected url for uploading.
expected_upload_path_ = kTestUploadExistingFilePath;
@@ -1573,6 +1649,65 @@ TEST_F(DriveApiRequestsTest,
EXPECT_FALSE(new_entry.get());
}
+TEST_F(DriveApiRequestsTest, UploadExistingFileWithMetadataRequest) {
+ const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123};
+ const base::Time::Exploded kLastViewedByMeDate =
+ {2013, 7, 0, 19, 15, 59, 13, 123};
+
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadExistingFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const std::string kTestContent(100, 'a');
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
+
+ // Initiate uploading a new file to the directory with "parent_resource_id".
+ {
+ base::RunLoop run_loop;
+ drive::InitiateUploadExistingFileRequest* request =
+ new drive::InitiateUploadExistingFileRequest(
+ request_sender_.get(),
+ *url_generator_,
+ kTestContentType,
+ kTestContent.size(),
+ "resource_id", // The resource id of the file to be overwritten.
+ kTestETag,
+ test_util::CreateQuitCallback(
+ &run_loop,
+ test_util::CreateCopyResultCallback(&error, &upload_url)));
+ request->set_parent_resource_id("new_parent_resource_id");
+ request->set_title("new file title");
+ request->set_modified_date(base::Time::FromUTCExploded(kModifiedDate));
+ request->set_last_viewed_by_me_date(
+ base::Time::FromUTCExploded(kLastViewedByMeDate));
+
+ request_sender_->StartRequestWithRetry(request);
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(kTestUploadExistingFilePath, upload_url.path());
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
+ http_request_.headers["X-Upload-Content-Length"]);
+ EXPECT_EQ(kTestETag, http_request_.headers["If-Match"]);
+
+ EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files/resource_id?"
+ "uploadType=resumable&setModifiedDate=true",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ("{\"lastViewedByMeDate\":\"2013-07-19T15:59:13.123Z\","
+ "\"modifiedDate\":\"2012-07-19T15:59:13.123Z\","
+ "\"parents\":[{\"id\":\"new_parent_resource_id\","
+ "\"kind\":\"drive#fileLink\"}],"
+ "\"title\":\"new file title\"}",
+ http_request_.content);
+}
+
TEST_F(DriveApiRequestsTest, DownloadFileRequest) {
const base::FilePath kDownloadedFilePath =
temp_dir_.path().AppendASCII("cache_file");
@@ -1644,4 +1779,77 @@ TEST_F(DriveApiRequestsTest, DownloadFileRequest_GetContentCallback) {
EXPECT_EQ(expected_contents, contents);
}
+TEST_F(DriveApiRequestsTest, PermissionsInsertRequest) {
+ expected_content_type_ = "application/json";
+ expected_content_ = kTestPermissionResponse;
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+
+ // Add comment permission to the user "user@example.com".
+ {
+ base::RunLoop run_loop;
+ drive::PermissionsInsertRequest* request =
+ new drive::PermissionsInsertRequest(
+ request_sender_.get(),
+ *url_generator_,
+ test_util::CreateQuitCallback(
+ &run_loop,
+ test_util::CreateCopyResultCallback(&error)));
+ request->set_id("resource_id");
+ request->set_role(drive::PERMISSION_ROLE_COMMENTER);
+ request->set_type(drive::PERMISSION_TYPE_USER);
+ request->set_value("user@example.com");
+ request_sender_->StartRequestWithRetry(request);
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
+ EXPECT_EQ("/drive/v2/files/resource_id/permissions",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+
+ scoped_ptr<base::Value> expected(base::JSONReader::Read(
+ "{\"additionalRoles\":[\"commenter\"], \"role\":\"reader\", "
+ "\"type\":\"user\",\"value\":\"user@example.com\"}"));
+ ASSERT_TRUE(expected);
+
+ scoped_ptr<base::Value> result(base::JSONReader::Read(http_request_.content));
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_TRUE(base::Value::Equals(expected.get(), result.get()));
+
+ // Add "can edit" permission to users in "example.com".
+ error = GDATA_OTHER_ERROR;
+ {
+ base::RunLoop run_loop;
+ drive::PermissionsInsertRequest* request =
+ new drive::PermissionsInsertRequest(
+ request_sender_.get(),
+ *url_generator_,
+ test_util::CreateQuitCallback(
+ &run_loop,
+ test_util::CreateCopyResultCallback(&error)));
+ request->set_id("resource_id2");
+ request->set_role(drive::PERMISSION_ROLE_WRITER);
+ request->set_type(drive::PERMISSION_TYPE_DOMAIN);
+ request->set_value("example.com");
+ request_sender_->StartRequestWithRetry(request);
+ run_loop.Run();
+ }
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
+ EXPECT_EQ("/drive/v2/files/resource_id2/permissions",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+
+ expected.reset(base::JSONReader::Read(
+ "{\"role\":\"writer\", \"type\":\"domain\",\"value\":\"example.com\"}"));
+ ASSERT_TRUE(expected);
+
+ result.reset(base::JSONReader::Read(http_request_.content));
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_TRUE(base::Value::Equals(expected.get(), result.get()));
+}
+
} // namespace google_apis
diff --git a/chromium/google_apis/drive/drive_api_url_generator.cc b/chromium/google_apis/drive/drive_api_url_generator.cc
index c12b947d3dc..b8374e27699 100644
--- a/chromium/google_apis/drive/drive_api_url_generator.cc
+++ b/chromium/google_apis/drive/drive_api_url_generator.cc
@@ -7,6 +7,7 @@
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
+#include "google_apis/google_api_keys.h"
#include "net/base/escape.h"
#include "net/base/url_util.h"
@@ -29,6 +30,14 @@ const char kDriveV2FileTrashUrlFormat[] = "/drive/v2/files/%s/trash";
const char kDriveV2InitiateUploadNewFileUrl[] = "/upload/drive/v2/files";
const char kDriveV2InitiateUploadExistingFileUrlPrefix[] =
"/upload/drive/v2/files/";
+const char kDriveV2PermissionsUrlFormat[] = "/drive/v2/files/%s/permissions";
+
+// apps.delete and file.authorize API is exposed through a special endpoint
+// v2internal that is accessible only by the official API key for Chrome.
+const char kDriveV2InternalAppsUrl[] = "/drive/v2internal/apps";
+const char kDriveV2AppsDeleteUrlFormat[] = "/drive/v2internal/apps/%s";
+const char kDriveV2FilesAuthorizeUrlFormat[] =
+ "/drive/v2internal/files/%s/authorize?appId=%s";
GURL AddResumableUploadParam(const GURL& url) {
return net::AppendOrReplaceQueryParameter(url, "uploadType", "resumable");
@@ -56,14 +65,28 @@ GURL DriveApiUrlGenerator::GetAboutGetUrl() const {
return base_url_.Resolve(kDriveV2AboutUrl);
}
-GURL DriveApiUrlGenerator::GetAppsListUrl() const {
- return base_url_.Resolve(kDriveV2AppsUrl);
+GURL DriveApiUrlGenerator::GetAppsListUrl(bool use_internal_endpoint) const {
+ return base_url_.Resolve(use_internal_endpoint ?
+ kDriveV2InternalAppsUrl : kDriveV2AppsUrl);
+}
+
+GURL DriveApiUrlGenerator::GetAppsDeleteUrl(const std::string& app_id) const {
+ return base_url_.Resolve(base::StringPrintf(
+ kDriveV2AppsDeleteUrlFormat, net::EscapePath(app_id).c_str()));
}
GURL DriveApiUrlGenerator::GetFilesGetUrl(const std::string& file_id) const {
return base_url_.Resolve(kDriveV2FileUrlPrefix + net::EscapePath(file_id));
}
+GURL DriveApiUrlGenerator::GetFilesAuthorizeUrl(
+ const std::string& file_id,
+ const std::string& app_id) const {
+ return base_url_.Resolve(base::StringPrintf(kDriveV2FilesAuthorizeUrlFormat,
+ net::EscapePath(file_id).c_str(),
+ net::EscapePath(app_id).c_str()));
+}
+
GURL DriveApiUrlGenerator::GetFilesInsertUrl() const {
return base_url_.Resolve(kDriveV2FilesUrl);
}
@@ -162,17 +185,31 @@ GURL DriveApiUrlGenerator::GetChildrenDeleteUrl(
net::EscapePath(child_id).c_str()));
}
-GURL DriveApiUrlGenerator::GetInitiateUploadNewFileUrl() const {
- return AddResumableUploadParam(
+GURL DriveApiUrlGenerator::GetInitiateUploadNewFileUrl(
+ bool set_modified_date) const {
+ GURL url = AddResumableUploadParam(
base_url_.Resolve(kDriveV2InitiateUploadNewFileUrl));
+
+ // setModifiedDate is "false" by default.
+ if (set_modified_date)
+ url = net::AppendOrReplaceQueryParameter(url, "setModifiedDate", "true");
+
+ return url;
}
GURL DriveApiUrlGenerator::GetInitiateUploadExistingFileUrl(
- const std::string& resource_id) const {
- const GURL& url = base_url_.Resolve(
+ const std::string& resource_id,
+ bool set_modified_date) const {
+ GURL url = base_url_.Resolve(
kDriveV2InitiateUploadExistingFileUrlPrefix +
net::EscapePath(resource_id));
- return AddResumableUploadParam(url);
+ url = AddResumableUploadParam(url);
+
+ // setModifiedDate is "false" by default.
+ if (set_modified_date)
+ url = net::AppendOrReplaceQueryParameter(url, "setModifiedDate", "true");
+
+ return url;
}
GURL DriveApiUrlGenerator::GenerateDownloadFileUrl(
@@ -180,4 +217,11 @@ GURL DriveApiUrlGenerator::GenerateDownloadFileUrl(
return base_download_url_.Resolve(net::EscapePath(resource_id));
}
+GURL DriveApiUrlGenerator::GetPermissionsInsertUrl(
+ const std::string& resource_id) const {
+ return base_url_.Resolve(
+ base::StringPrintf(kDriveV2PermissionsUrlFormat,
+ net::EscapePath(resource_id).c_str()));
+}
+
} // namespace google_apis
diff --git a/chromium/google_apis/drive/drive_api_url_generator.h b/chromium/google_apis/drive/drive_api_url_generator.h
index cf93edd3255..8c4b160a79f 100644
--- a/chromium/google_apis/drive/drive_api_url_generator.h
+++ b/chromium/google_apis/drive/drive_api_url_generator.h
@@ -30,11 +30,20 @@ class DriveApiUrlGenerator {
GURL GetAboutGetUrl() const;
// Returns a URL to invoke "Apps: list" method.
- GURL GetAppsListUrl() const;
+ // Set |use_internal_endpoint| to true if official Chrome's API key is used
+ // and retrieving more information (related to App uninstall) is necessary.
+ GURL GetAppsListUrl(bool use_internal_endpoint) const;
+
+ // Returns a URL to uninstall an app with the give |app_id|.
+ GURL GetAppsDeleteUrl(const std::string& app_id) const;
// Returns a URL to fetch a file metadata.
GURL GetFilesGetUrl(const std::string& file_id) const;
+ // Returns a URL to authorize an app to access a file.
+ GURL GetFilesAuthorizeUrl(const std::string& file_id,
+ const std::string& app_id) const;
+
// Returns a URL to create a resource.
GURL GetFilesInsertUrl() const;
@@ -72,15 +81,19 @@ class DriveApiUrlGenerator {
const std::string& folder_id) const;
// Returns a URL to initiate uploading a new file.
- GURL GetInitiateUploadNewFileUrl() const;
+ GURL GetInitiateUploadNewFileUrl(bool set_modified_date) const;
// Returns a URL to initiate uploading an existing file specified by
// |resource_id|.
- GURL GetInitiateUploadExistingFileUrl(const std::string& resource_id) const;
+ GURL GetInitiateUploadExistingFileUrl(const std::string& resource_id,
+ bool set_modified_date) const;
// Generates a URL for downloading a file.
GURL GenerateDownloadFileUrl(const std::string& resource_id) const;
+ // Generates a URL for adding permissions.
+ GURL GetPermissionsInsertUrl(const std::string& resource_id) const;
+
private:
const GURL base_url_;
const GURL base_download_url_;
diff --git a/chromium/google_apis/drive/drive_api_url_generator_unittest.cc b/chromium/google_apis/drive/drive_api_url_generator_unittest.cc
index 343b2791c4e..557a0af3f82 100644
--- a/chromium/google_apis/drive/drive_api_url_generator_unittest.cc
+++ b/chromium/google_apis/drive/drive_api_url_generator_unittest.cc
@@ -35,10 +35,20 @@ TEST_F(DriveApiUrlGeneratorTest, GetAboutGetUrl) {
}
TEST_F(DriveApiUrlGeneratorTest, GetAppsListUrl) {
+ const bool use_internal_url = true;
+ EXPECT_EQ("https://www.googleapis.com/drive/v2internal/apps",
+ url_generator_.GetAppsListUrl(use_internal_url).spec());
EXPECT_EQ("https://www.googleapis.com/drive/v2/apps",
- url_generator_.GetAppsListUrl().spec());
+ url_generator_.GetAppsListUrl(!use_internal_url).spec());
EXPECT_EQ("http://127.0.0.1:12345/drive/v2/apps",
- test_url_generator_.GetAppsListUrl().spec());
+ test_url_generator_.GetAppsListUrl(!use_internal_url).spec());
+}
+
+TEST_F(DriveApiUrlGeneratorTest, GetAppsDeleteUrl) {
+ EXPECT_EQ("https://www.googleapis.com/drive/v2internal/apps/0ADK06pfg",
+ url_generator_.GetAppsDeleteUrl("0ADK06pfg").spec());
+ EXPECT_EQ("http://127.0.0.1:12345/drive/v2internal/apps/0ADK06pfg",
+ test_url_generator_.GetAppsDeleteUrl("0ADK06pfg").spec());
}
TEST_F(DriveApiUrlGeneratorTest, GetFilesGetUrl) {
@@ -58,6 +68,15 @@ TEST_F(DriveApiUrlGeneratorTest, GetFilesGetUrl) {
test_url_generator_.GetFilesGetUrl("file:file_id").spec());
}
+TEST_F(DriveApiUrlGeneratorTest, GetFilesAuthorizeUrl) {
+ EXPECT_EQ(
+ "https://www.googleapis.com/drive/v2internal/files/aa/authorize?appId=bb",
+ url_generator_.GetFilesAuthorizeUrl("aa", "bb").spec());
+ EXPECT_EQ(
+ "http://127.0.0.1:12345/drive/v2internal/files/foo/authorize?appId=bar",
+ test_url_generator_.GetFilesAuthorizeUrl("foo", "bar").spec());
+}
+
TEST_F(DriveApiUrlGeneratorTest, GetFilesInsertUrl) {
EXPECT_EQ("https://www.googleapis.com/drive/v2/files",
url_generator_.GetFilesInsertUrl().spec());
@@ -338,45 +357,69 @@ TEST_F(DriveApiUrlGeneratorTest, GetChildrenDeleteUrl) {
}
TEST_F(DriveApiUrlGeneratorTest, GetInitiateUploadNewFileUrl) {
+ const bool kSetModifiedDate = true;
+
EXPECT_EQ(
"https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable",
- url_generator_.GetInitiateUploadNewFileUrl().spec());
+ url_generator_.GetInitiateUploadNewFileUrl(!kSetModifiedDate).spec());
EXPECT_EQ(
"http://127.0.0.1:12345/upload/drive/v2/files?uploadType=resumable",
- test_url_generator_.GetInitiateUploadNewFileUrl().spec());
+ test_url_generator_.GetInitiateUploadNewFileUrl(
+ !kSetModifiedDate).spec());
+
+ EXPECT_EQ(
+ "http://127.0.0.1:12345/upload/drive/v2/files?uploadType=resumable&"
+ "setModifiedDate=true",
+ test_url_generator_.GetInitiateUploadNewFileUrl(
+ kSetModifiedDate).spec());
}
TEST_F(DriveApiUrlGeneratorTest, GetInitiateUploadExistingFileUrl) {
+ const bool kSetModifiedDate = true;
+
// |resource_id| should be embedded into the url.
EXPECT_EQ(
"https://www.googleapis.com/upload/drive/v2/files/0ADK06pfg"
"?uploadType=resumable",
- url_generator_.GetInitiateUploadExistingFileUrl("0ADK06pfg").spec());
+ url_generator_.GetInitiateUploadExistingFileUrl(
+ "0ADK06pfg", !kSetModifiedDate).spec());
EXPECT_EQ(
"https://www.googleapis.com/upload/drive/v2/files/0Bz0bd074"
"?uploadType=resumable",
- url_generator_.GetInitiateUploadExistingFileUrl("0Bz0bd074").spec());
+ url_generator_.GetInitiateUploadExistingFileUrl(
+ "0Bz0bd074", !kSetModifiedDate).spec());
EXPECT_EQ(
"https://www.googleapis.com/upload/drive/v2/files/file%3Afile_id"
"?uploadType=resumable",
- url_generator_.GetInitiateUploadExistingFileUrl("file:file_id").spec());
+ url_generator_.GetInitiateUploadExistingFileUrl(
+ "file:file_id", !kSetModifiedDate).spec());
+ EXPECT_EQ(
+ "https://www.googleapis.com/upload/drive/v2/files/file%3Afile_id"
+ "?uploadType=resumable&setModifiedDate=true",
+ url_generator_.GetInitiateUploadExistingFileUrl(
+ "file:file_id", kSetModifiedDate).spec());
EXPECT_EQ(
"http://127.0.0.1:12345/upload/drive/v2/files/0ADK06pfg"
"?uploadType=resumable",
test_url_generator_.GetInitiateUploadExistingFileUrl(
- "0ADK06pfg").spec());
+ "0ADK06pfg", !kSetModifiedDate).spec());
EXPECT_EQ(
"http://127.0.0.1:12345/upload/drive/v2/files/0Bz0bd074"
"?uploadType=resumable",
test_url_generator_.GetInitiateUploadExistingFileUrl(
- "0Bz0bd074").spec());
+ "0Bz0bd074", !kSetModifiedDate).spec());
EXPECT_EQ(
"http://127.0.0.1:12345/upload/drive/v2/files/file%3Afile_id"
"?uploadType=resumable",
test_url_generator_.GetInitiateUploadExistingFileUrl(
- "file:file_id").spec());
+ "file:file_id", !kSetModifiedDate).spec());
+ EXPECT_EQ(
+ "http://127.0.0.1:12345/upload/drive/v2/files/file%3Afile_id"
+ "?uploadType=resumable&setModifiedDate=true",
+ test_url_generator_.GetInitiateUploadExistingFileUrl(
+ "file:file_id", kSetModifiedDate).spec());
}
TEST_F(DriveApiUrlGeneratorTest, GenerateDownloadFileUrl) {
@@ -391,4 +434,11 @@ TEST_F(DriveApiUrlGeneratorTest, GenerateDownloadFileUrl) {
test_url_generator_.GenerateDownloadFileUrl("resourceId").spec());
}
+TEST_F(DriveApiUrlGeneratorTest, GeneratePermissionsInsertUrl) {
+ EXPECT_EQ("https://www.googleapis.com/drive/v2/files/0ADK06pfg/permissions",
+ url_generator_.GetPermissionsInsertUrl("0ADK06pfg").spec());
+ EXPECT_EQ("http://127.0.0.1:12345/drive/v2/files/file%3Aabc/permissions",
+ test_url_generator_.GetPermissionsInsertUrl("file:abc").spec());
+}
+
} // namespace google_apis
diff --git a/chromium/google_apis/drive/drive_common_callbacks.h b/chromium/google_apis/drive/drive_common_callbacks.h
index c31bea0358e..234c531f2b5 100644
--- a/chromium/google_apis/drive/drive_common_callbacks.h
+++ b/chromium/google_apis/drive/drive_common_callbacks.h
@@ -40,11 +40,6 @@ typedef base::Callback<void(GDataErrorCode error,
typedef base::Callback<void(GDataErrorCode error,
scoped_ptr<AppList> app_list)> AppListCallback;
-// Callback used for handling UploadRangeResponse.
-typedef base::Callback<void(
- const UploadRangeResponse& response,
- scoped_ptr<ResourceEntry> new_entry)> UploadRangeCallback;
-
// Callback used for authorizing an app. |open_url| is used to open the target
// file with the authorized app.
typedef base::Callback<void(GDataErrorCode error,
diff --git a/chromium/google_apis/drive/gdata_contacts_requests.cc b/chromium/google_apis/drive/gdata_contacts_requests.cc
deleted file mode 100644
index 11419af449b..00000000000
--- a/chromium/google_apis/drive/gdata_contacts_requests.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2012 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 "google_apis/drive/gdata_contacts_requests.h"
-
-#include "google_apis/drive/time_util.h"
-#include "net/base/url_util.h"
-#include "url/gurl.h"
-
-namespace google_apis {
-
-namespace {
-
-// URL requesting all contact groups.
-const char kGetContactGroupsURL[] =
- "https://www.google.com/m8/feeds/groups/default/full?alt=json";
-
-// URL requesting all contacts.
-// TODO(derat): Per https://goo.gl/AufHP, "The feed may not contain all of the
-// user's contacts, because there's a default limit on the number of results
-// returned." Decide if 10000 is reasonable or not.
-const char kGetContactsURL[] =
- "https://www.google.com/m8/feeds/contacts/default/full"
- "?alt=json&showdeleted=true&max-results=10000";
-
-// Query parameter optionally appended to |kGetContactsURL| to return contacts
-// from a specific group (as opposed to all contacts).
-const char kGetContactsGroupParam[] = "group";
-
-// Query parameter optionally appended to |kGetContactsURL| to return only
-// recently-updated contacts.
-const char kGetContactsUpdatedMinParam[] = "updated-min";
-
-} // namespace
-
-//========================== GetContactGroupsRequest =========================
-
-GetContactGroupsRequest::GetContactGroupsRequest(
- RequestSender* runner,
- const GetDataCallback& callback)
- : GetDataRequest(runner, callback) {
-}
-
-GetContactGroupsRequest::~GetContactGroupsRequest() {}
-
-GURL GetContactGroupsRequest::GetURL() const {
- return !feed_url_for_testing_.is_empty() ?
- feed_url_for_testing_ :
- GURL(kGetContactGroupsURL);
-}
-
-//============================ GetContactsRequest ============================
-
-GetContactsRequest::GetContactsRequest(
- RequestSender* runner,
- const std::string& group_id,
- const base::Time& min_update_time,
- const GetDataCallback& callback)
- : GetDataRequest(runner, callback),
- group_id_(group_id),
- min_update_time_(min_update_time) {
-}
-
-GetContactsRequest::~GetContactsRequest() {}
-
-GURL GetContactsRequest::GetURL() const {
- if (!feed_url_for_testing_.is_empty())
- return GURL(feed_url_for_testing_);
-
- GURL url(kGetContactsURL);
-
- if (!group_id_.empty()) {
- url = net::AppendQueryParameter(url, kGetContactsGroupParam, group_id_);
- }
- if (!min_update_time_.is_null()) {
- std::string time_rfc3339 = util::FormatTimeAsString(min_update_time_);
- url = net::AppendQueryParameter(
- url, kGetContactsUpdatedMinParam, time_rfc3339);
- }
- return url;
-}
-
-//========================== GetContactPhotoRequest ==========================
-
-GetContactPhotoRequest::GetContactPhotoRequest(
- RequestSender* runner,
- const GURL& photo_url,
- const GetContentCallback& callback)
- : UrlFetchRequestBase(runner),
- photo_url_(photo_url),
- callback_(callback) {
-}
-
-GetContactPhotoRequest::~GetContactPhotoRequest() {}
-
-GURL GetContactPhotoRequest::GetURL() const {
- return photo_url_;
-}
-
-void GetContactPhotoRequest::ProcessURLFetchResults(
- const net::URLFetcher* source) {
- GDataErrorCode code = GetErrorCode();
- scoped_ptr<std::string> data(new std::string(response_writer()->data()));
- callback_.Run(code, data.Pass());
- OnProcessURLFetchResultsComplete();
-}
-
-void GetContactPhotoRequest::RunCallbackOnPrematureFailure(
- GDataErrorCode code) {
- scoped_ptr<std::string> data(new std::string);
- callback_.Run(code, data.Pass());
-}
-
-} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_contacts_requests.h b/chromium/google_apis/drive/gdata_contacts_requests.h
deleted file mode 100644
index 05ce693242d..00000000000
--- a/chromium/google_apis/drive/gdata_contacts_requests.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2012 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.
-
-#ifndef GOOGLE_APIS_DRIVE_GDATA_CONTACTS_REQUESTS_H_
-#define GOOGLE_APIS_DRIVE_GDATA_CONTACTS_REQUESTS_H_
-
-#include <string>
-
-#include "base/time/time.h"
-#include "google_apis/drive/base_requests.h"
-
-namespace google_apis {
-
-//========================== GetContactGroupsRequest =========================
-
-// This class fetches a JSON feed containing a user's contact groups.
-class GetContactGroupsRequest : public GetDataRequest {
- public:
- GetContactGroupsRequest(RequestSender* runner,
- const GetDataCallback& callback);
- virtual ~GetContactGroupsRequest();
-
- void set_feed_url_for_testing(const GURL& url) {
- feed_url_for_testing_ = url;
- }
-
- protected:
- // Overridden from GetDataRequest.
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- // If non-empty, URL of the feed to fetch.
- GURL feed_url_for_testing_;
-
- DISALLOW_COPY_AND_ASSIGN(GetContactGroupsRequest);
-};
-
-//============================ GetContactsRequest ============================
-
-// This class fetches a JSON feed containing a user's contacts.
-class GetContactsRequest : public GetDataRequest {
- public:
- GetContactsRequest(RequestSender* runner,
- const std::string& group_id,
- const base::Time& min_update_time,
- const GetDataCallback& callback);
- virtual ~GetContactsRequest();
-
- void set_feed_url_for_testing(const GURL& url) {
- feed_url_for_testing_ = url;
- }
-
- protected:
- // Overridden from GetDataRequest.
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- // If non-empty, URL of the feed to fetch.
- GURL feed_url_for_testing_;
-
- // If non-empty, contains the ID of the group whose contacts should be
- // returned. Group IDs generally look like this:
- // http://www.google.com/m8/feeds/groups/user%40gmail.com/base/6
- std::string group_id_;
-
- // If is_null() is false, contains a minimum last-updated time that will be
- // used to filter contacts.
- base::Time min_update_time_;
-
- DISALLOW_COPY_AND_ASSIGN(GetContactsRequest);
-};
-
-//========================== GetContactPhotoRequest ==========================
-
-// This class fetches a contact's photo.
-class GetContactPhotoRequest : public UrlFetchRequestBase {
- public:
- GetContactPhotoRequest(RequestSender* runner,
- const GURL& photo_url,
- const GetContentCallback& callback);
- virtual ~GetContactPhotoRequest();
-
- protected:
- // Overridden from UrlFetchRequestBase.
- virtual GURL GetURL() const OVERRIDE;
- virtual void ProcessURLFetchResults(const net::URLFetcher* source) OVERRIDE;
- virtual void RunCallbackOnPrematureFailure(GDataErrorCode code) OVERRIDE;
-
- private:
- // Location of the photo to fetch.
- GURL photo_url_;
-
- // Callback to which the photo data is passed.
- GetContentCallback callback_;
-
- DISALLOW_COPY_AND_ASSIGN(GetContactPhotoRequest);
-};
-
-} // namespace google_apis
-
-#endif // GOOGLE_APIS_DRIVE_GDATA_CONTACTS_REQUESTS_H_
diff --git a/chromium/google_apis/drive/gdata_wapi_parser.cc b/chromium/google_apis/drive/gdata_wapi_parser.cc
index 82f9d43151f..9c50e5ab524 100644
--- a/chromium/google_apis/drive/gdata_wapi_parser.cc
+++ b/chromium/google_apis/drive/gdata_wapi_parser.cc
@@ -46,23 +46,6 @@ const char kFeedLinkField[] = "gd$feedLink";
const char kFileNameField[] = "docs$filename.$t";
const char kHrefField[] = "href";
const char kIDField[] = "id.$t";
-const char kInstalledAppField[] = "docs$installedApp";
-const char kInstalledAppNameField[] = "docs$installedAppName";
-const char kInstalledAppIdField[] = "docs$installedAppId";
-const char kInstalledAppIconField[] = "docs$installedAppIcon";
-const char kInstalledAppIconCategoryField[] = "docs$installedAppIconCategory";
-const char kInstalledAppIconSizeField[] = "docs$installedAppIconSize";
-const char kInstalledAppObjectTypeField[] = "docs$installedAppObjectType";
-const char kInstalledAppPrimaryFileExtensionField[] =
- "docs$installedAppPrimaryFileExtension";
-const char kInstalledAppPrimaryMimeTypeField[] =
- "docs$installedAppPrimaryMimeType";
-const char kInstalledAppSecondaryFileExtensionField[] =
- "docs$installedAppSecondaryFileExtension";
-const char kInstalledAppSecondaryMimeTypeField[] =
- "docs$installedAppSecondaryMimeType";
-const char kInstalledAppSupportsCreateField[] =
- "docs$installedAppSupportsCreate";
const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
const char kLabelField[] = "label";
const char kLargestChangestampField[] = "docs$largestChangestamp.value";
@@ -71,8 +54,6 @@ const char kLinkField[] = "link";
const char kMD5Field[] = "docs$md5Checksum.$t";
const char kNameField[] = "name.$t";
const char kPublishedField[] = "published.$t";
-const char kQuotaBytesTotalField[] = "gd$quotaBytesTotal.$t";
-const char kQuotaBytesUsedField[] = "gd$quotaBytesUsed.$t";
const char kRelField[] = "rel";
const char kRemovedField[] = "docs$removed";
const char kResourceIdField[] = "gd$resourceId.$t";
@@ -81,7 +62,6 @@ const char kSizeField[] = "docs$size.$t";
const char kSrcField[] = "src";
const char kStartIndexField[] = "openSearch$startIndex.$t";
const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
-const char kTField[] = "$t";
const char kTermField[] = "term";
const char kTitleField[] = "title";
const char kTitleTField[] = "title.$t";
@@ -186,17 +166,6 @@ const CategoryTypeMap kCategoryTypeMap[] = {
{ Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" },
};
-struct AppIconCategoryMap {
- AppIcon::IconCategory category;
- const char* category_name;
-};
-
-const AppIconCategoryMap kAppIconCategoryMap[] = {
- { AppIcon::ICON_DOCUMENT, "document" },
- { AppIcon::ICON_APPLICATION, "application" },
- { AppIcon::ICON_SHARED_DOCUMENT, "documentShared" },
-};
-
// Converts |url_string| to |result|. Always returns true to be used
// for JSONValueConverter::RegisterCustomField method.
// TODO(mukai): make it return false in case of invalid |url_string|.
@@ -205,17 +174,6 @@ bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
return true;
}
-// Converts boolean string values like "true" into bool.
-bool GetBoolFromString(const base::StringPiece& value, bool* result) {
- *result = (value == "true");
- return true;
-}
-
-bool SortBySize(const InstalledApp::IconList::value_type& a,
- const InstalledApp::IconList::value_type& b) {
- return a.first < b.first;
-}
-
} // namespace
////////////////////////////////////////////////////////////////////////////////
@@ -378,49 +336,6 @@ void Content::RegisterJSONConverter(
}
////////////////////////////////////////////////////////////////////////////////
-// AppIcon implementation
-
-AppIcon::AppIcon() : category_(AppIcon::ICON_UNKNOWN), icon_side_length_(0) {
-}
-
-AppIcon::~AppIcon() {
-}
-
-// static
-void AppIcon::RegisterJSONConverter(
- base::JSONValueConverter<AppIcon>* converter) {
- converter->RegisterCustomField<AppIcon::IconCategory>(
- kInstalledAppIconCategoryField,
- &AppIcon::category_,
- &AppIcon::GetIconCategory);
- converter->RegisterCustomField<int>(kInstalledAppIconSizeField,
- &AppIcon::icon_side_length_,
- base::StringToInt);
- converter->RegisterRepeatedMessage(kLinkField, &AppIcon::links_);
-}
-
-GURL AppIcon::GetIconURL() const {
- for (size_t i = 0; i < links_.size(); ++i) {
- if (links_[i]->type() == Link::LINK_ICON)
- return links_[i]->href();
- }
- return GURL();
-}
-
-// static
-bool AppIcon::GetIconCategory(const base::StringPiece& category,
- AppIcon::IconCategory* result) {
- for (size_t i = 0; i < arraysize(kAppIconCategoryMap); i++) {
- if (category == kAppIconCategoryMap[i].category_name) {
- *result = kAppIconCategoryMap[i].category;
- return true;
- }
- }
- DVLOG(1) << "Unknown icon category " << category;
- return false;
-}
-
-////////////////////////////////////////////////////////////////////////////////
// CommonMetadata implementation
CommonMetadata::CommonMetadata() {
@@ -523,9 +438,10 @@ void ResourceEntry::RegisterJSONConverter(
// ImageMediaMetadata fields are not supported by WAPI.
}
-std::string ResourceEntry::GetHostedDocumentExtension() const {
+// static
+std::string ResourceEntry::GetHostedDocumentExtension(DriveEntryKind kind) {
for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
- if (kEntryKindMap[i].kind == kind_) {
+ if (kEntryKindMap[i].kind == kind) {
if (kEntryKindMap[i].extension)
return std::string(kEntryKindMap[i].extension);
else
@@ -536,19 +452,25 @@ std::string ResourceEntry::GetHostedDocumentExtension() const {
}
// static
+DriveEntryKind ResourceEntry::GetEntryKindFromExtension(
+ const std::string& extension) {
+ for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
+ const char* document_extension = kEntryKindMap[i].extension;
+ if (document_extension && extension == document_extension)
+ return kEntryKindMap[i].kind;
+ }
+ return ENTRY_KIND_UNKNOWN;
+}
+
+// static
int ResourceEntry::ClassifyEntryKindByFileExtension(
const base::FilePath& file_path) {
#if defined(OS_WIN)
- std::string file_extension = WideToUTF8(file_path.Extension());
+ std::string file_extension = base::WideToUTF8(file_path.Extension());
#else
std::string file_extension = file_path.Extension();
#endif
- for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
- const char* document_extension = kEntryKindMap[i].extension;
- if (document_extension && file_extension == document_extension)
- return ClassifyEntryKind(kEntryKindMap[i].kind);
- }
- return 0;
+ return ClassifyEntryKind(GetEntryKindFromExtension(file_extension));
}
// static
@@ -742,142 +664,4 @@ void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
entries_.release(entries);
}
-////////////////////////////////////////////////////////////////////////////////
-// InstalledApp implementation
-
-InstalledApp::InstalledApp() : supports_create_(false) {
-}
-
-InstalledApp::~InstalledApp() {
-}
-
-InstalledApp::IconList InstalledApp::GetIconsForCategory(
- AppIcon::IconCategory category) const {
- IconList result;
-
- for (ScopedVector<AppIcon>::const_iterator icon_iter = app_icons_.begin();
- icon_iter != app_icons_.end(); ++icon_iter) {
- if ((*icon_iter)->category() != category)
- continue;
- GURL icon_url = (*icon_iter)->GetIconURL();
- if (icon_url.is_empty())
- continue;
- result.push_back(std::make_pair((*icon_iter)->icon_side_length(),
- icon_url));
- }
-
- // Return a sorted list, smallest to largest.
- std::sort(result.begin(), result.end(), SortBySize);
- return result;
-}
-
-GURL InstalledApp::GetProductUrl() const {
- for (ScopedVector<Link>::const_iterator it = links_.begin();
- it != links_.end(); ++it) {
- const Link* link = *it;
- if (link->type() == Link::LINK_PRODUCT)
- return link->href();
- }
- return GURL();
-}
-
-// static
-bool InstalledApp::GetValueString(const base::Value* value,
- std::string* result) {
- const base::DictionaryValue* dict = NULL;
- if (!value->GetAsDictionary(&dict))
- return false;
-
- if (!dict->GetString(kTField, result))
- return false;
-
- return true;
-}
-
-// static
-void InstalledApp::RegisterJSONConverter(
- base::JSONValueConverter<InstalledApp>* converter) {
- converter->RegisterRepeatedMessage(kInstalledAppIconField,
- &InstalledApp::app_icons_);
- converter->RegisterStringField(kInstalledAppIdField,
- &InstalledApp::app_id_);
- converter->RegisterStringField(kInstalledAppNameField,
- &InstalledApp::app_name_);
- converter->RegisterStringField(kInstalledAppObjectTypeField,
- &InstalledApp::object_type_);
- converter->RegisterCustomField<bool>(kInstalledAppSupportsCreateField,
- &InstalledApp::supports_create_,
- &GetBoolFromString);
- converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryMimeTypeField,
- &InstalledApp::primary_mimetypes_,
- &GetValueString);
- converter->RegisterRepeatedCustomValue(kInstalledAppSecondaryMimeTypeField,
- &InstalledApp::secondary_mimetypes_,
- &GetValueString);
- converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryFileExtensionField,
- &InstalledApp::primary_extensions_,
- &GetValueString);
- converter->RegisterRepeatedCustomValue(
- kInstalledAppSecondaryFileExtensionField,
- &InstalledApp::secondary_extensions_,
- &GetValueString);
- converter->RegisterRepeatedMessage(kLinkField, &InstalledApp::links_);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// AccountMetadata implementation
-
-AccountMetadata::AccountMetadata()
- : quota_bytes_total_(0),
- quota_bytes_used_(0),
- largest_changestamp_(0) {
-}
-
-AccountMetadata::~AccountMetadata() {
-}
-
-// static
-void AccountMetadata::RegisterJSONConverter(
- base::JSONValueConverter<AccountMetadata>* converter) {
- converter->RegisterCustomField<int64>(
- kQuotaBytesTotalField,
- &AccountMetadata::quota_bytes_total_,
- &base::StringToInt64);
- converter->RegisterCustomField<int64>(
- kQuotaBytesUsedField,
- &AccountMetadata::quota_bytes_used_,
- &base::StringToInt64);
- converter->RegisterCustomField<int64>(
- kLargestChangestampField,
- &AccountMetadata::largest_changestamp_,
- &base::StringToInt64);
- converter->RegisterRepeatedMessage(kInstalledAppField,
- &AccountMetadata::installed_apps_);
-}
-
-// static
-scoped_ptr<AccountMetadata> AccountMetadata::CreateFrom(
- const base::Value& value) {
- scoped_ptr<AccountMetadata> metadata(new AccountMetadata());
- const base::DictionaryValue* dictionary = NULL;
- const base::Value* entry = NULL;
- if (!value.GetAsDictionary(&dictionary) ||
- !dictionary->Get(kEntryField, &entry) ||
- !metadata->Parse(*entry)) {
- LOG(ERROR) << "Unable to create: Invalid account metadata feed!";
- return scoped_ptr<AccountMetadata>();
- }
-
- return metadata.Pass();
-}
-
-bool AccountMetadata::Parse(const base::Value& value) {
- base::JSONValueConverter<AccountMetadata> converter;
- if (!converter.Convert(value, this)) {
- LOG(ERROR) << "Unable to parse: Invalid account metadata feed!";
- return false;
- }
- return true;
-}
-
} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_wapi_parser.h b/chromium/google_apis/drive/gdata_wapi_parser.h
index 0c89c11b09e..1081dbcf9cf 100644
--- a/chromium/google_apis/drive/gdata_wapi_parser.h
+++ b/chromium/google_apis/drive/gdata_wapi_parser.h
@@ -251,60 +251,6 @@ class Content {
std::string mime_type_;
};
-// This stores a representation of an application icon as registered with the
-// installed applications section of the account metadata feed. There can be
-// multiple icons registered for each application, differing in size, category
-// and MIME type.
-class AppIcon {
- public:
- enum IconCategory {
- ICON_UNKNOWN, // Uninitialized state
- ICON_DOCUMENT, // Document icon for various MIME types
- ICON_APPLICATION, // Application icon for various MIME types
- ICON_SHARED_DOCUMENT, // Icon for documents that are shared from other
- // users.
- };
-
- AppIcon();
- ~AppIcon();
-
- // Registers the mapping between JSON field names and the members in
- // this class.
- static void RegisterJSONConverter(
- base::JSONValueConverter<AppIcon>* converter);
-
- // Category of the icon.
- IconCategory category() const { return category_; }
-
- // Size in pixels of one side of the icon (icons are always square).
- int icon_side_length() const { return icon_side_length_; }
-
- // Get a list of links available for this AppIcon.
- const ScopedVector<Link>& links() const { return links_; }
-
- // Get the icon URL from the internal list of links. Returns the first
- // icon URL found in the list.
- GURL GetIconURL() const;
-
- void set_category(IconCategory category) { category_ = category; }
- void set_icon_side_length(int icon_side_length) {
- icon_side_length_ = icon_side_length;
- }
- void set_links(ScopedVector<Link> links) { links_ = links.Pass(); }
-
- private:
- // Extracts the icon category from the given string. Returns false and does
- // not change |result| when |scheme| has an unrecognizable value.
- static bool GetIconCategory(const base::StringPiece& category,
- IconCategory* result);
-
- IconCategory category_;
- int icon_side_length_;
- ScopedVector<Link> links_;
-
- DISALLOW_COPY_AND_ASSIGN(AppIcon);
-};
-
// Base class for feed entries. This class defines fields commonly used by
// various feeds.
class CommonMetadata {
@@ -464,13 +410,17 @@ class ResourceEntry : public CommonMetadata {
// If doesn't exist, then equals -1.
int64 image_rotation() const { return image_rotation_; }
+ // The time of this modification.
+ // Note: This property is filled only when converted from ChangeResource.
+ const base::Time& modification_date() const { return modification_date_; }
+
// Text version of resource entry kind. Returns an empty string for
// unknown entry kind.
std::string GetEntryKindText() const;
// Returns preferred file extension for hosted documents. If entry is not
// a hosted document, this call returns an empty string.
- std::string GetHostedDocumentExtension() const;
+ static std::string GetHostedDocumentExtension(DriveEntryKind kind);
// True if resource entry is remotely hosted.
bool is_hosted_document() const {
@@ -509,6 +459,9 @@ class ResourceEntry : public CommonMetadata {
KIND_OF_FILE = 1 << 4,
};
+ // Returns the kind enum corresponding to the extension in form ".xxx".
+ static DriveEntryKind GetEntryKindFromExtension(const std::string& extension);
+
// Classifies the EntryKind. The returned value is a bitmask of
// EntryKindClass. For example, DOCUMENT is classified as
// KIND_OF_HOSTED_DOCUMENT and KIND_OF_GOOGLE_DOCUMENT, hence the returned
@@ -554,6 +507,9 @@ class ResourceEntry : public CommonMetadata {
void set_image_rotation(int64 image_rotation) {
image_rotation_ = image_rotation;
}
+ void set_modification_date(const base::Time& modification_date) {
+ modification_date_ = modification_date;
+ }
// Fills the remaining fields where JSONValueConverter cannot catch.
// Currently, sets |kind_| and |labels_| based on the |categories_| in the
@@ -592,6 +548,8 @@ class ResourceEntry : public CommonMetadata {
int64 image_height_;
int64 image_rotation_;
+ base::Time modification_date_;
+
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
};
@@ -682,185 +640,6 @@ class ResourceList : public CommonMetadata {
DISALLOW_COPY_AND_ASSIGN(ResourceList);
};
-// Metadata representing installed Google Drive application.
-class InstalledApp {
- public:
- typedef std::vector<std::pair<int, GURL> > IconList;
-
- InstalledApp();
- virtual ~InstalledApp();
-
- // WebApp name.
- const std::string& app_name() const { return app_name_; }
-
- // Drive app id
- const std::string& app_id() const { return app_id_; }
-
- // Object (file) type name that is generated by this WebApp.
- const std::string& object_type() const { return object_type_; }
-
- // True if WebApp supports creation of new file instances.
- bool supports_create() const { return supports_create_; }
-
- // List of primary mime types supported by this WebApp. Primary status should
- // trigger this WebApp becoming the default handler of file instances that
- // have these mime types.
- const ScopedVector<std::string>& primary_mimetypes() const {
- return primary_mimetypes_;
- }
-
- // List of secondary mime types supported by this WebApp. Secondary status
- // should make this WebApp show up in "Open with..." pop-up menu of the
- // default action menu for file with matching mime types.
- const ScopedVector<std::string>& secondary_mimetypes() const {
- return secondary_mimetypes_;
- }
-
- // List of primary file extensions supported by this WebApp. Primary status
- // should trigger this WebApp becoming the default handler of file instances
- // that match these extensions.
- const ScopedVector<std::string>& primary_extensions() const {
- return primary_extensions_;
- }
-
- // List of secondary file extensions supported by this WebApp. Secondary
- // status should make this WebApp show up in "Open with..." pop-up menu of the
- // default action menu for file with matching extensions.
- const ScopedVector<std::string>& secondary_extensions() const {
- return secondary_extensions_;
- }
-
- // List of entry links.
- const ScopedVector<Link>& links() const { return links_; }
-
- // Returns a list of icons associated with this installed application.
- const ScopedVector<AppIcon>& app_icons() const {
- return app_icons_;
- }
-
- // Convenience function for getting the icon URLs for a particular |category|
- // of icon. Icons are returned in a sorted list, from smallest to largest.
- IconList GetIconsForCategory(AppIcon::IconCategory category) const;
-
- // Retrieves product URL from the link collection.
- GURL GetProductUrl() const;
-
- // Registers the mapping between JSON field names and the members in
- // this class.
- static void RegisterJSONConverter(
- base::JSONValueConverter<InstalledApp>* converter);
-
- void set_app_id(const std::string& app_id) { app_id_ = app_id; }
- void set_app_name(const std::string& app_name) { app_name_ = app_name; }
- void set_object_type(const std::string& object_type) {
- object_type_ = object_type;
- }
- void set_supports_create(bool supports_create) {
- supports_create_ = supports_create;
- }
- void set_primary_mimetypes(
- ScopedVector<std::string> primary_mimetypes) {
- primary_mimetypes_ = primary_mimetypes.Pass();
- }
- void set_secondary_mimetypes(
- ScopedVector<std::string> secondary_mimetypes) {
- secondary_mimetypes_ = secondary_mimetypes.Pass();
- }
- void set_primary_extensions(
- ScopedVector<std::string> primary_extensions) {
- primary_extensions_ = primary_extensions.Pass();
- }
- void set_secondary_extensions(
- ScopedVector<std::string> secondary_extensions) {
- secondary_extensions_ = secondary_extensions.Pass();
- }
- void set_links(ScopedVector<Link> links) {
- links_ = links.Pass();
- }
- void set_app_icons(ScopedVector<AppIcon> app_icons) {
- app_icons_ = app_icons.Pass();
- }
-
- private:
- // Extracts "$t" value from the dictionary |value| and returns it in |result|.
- // If the string value can't be found, it returns false.
- static bool GetValueString(const base::Value* value,
- std::string* result);
-
- std::string app_id_;
- std::string app_name_;
- std::string object_type_;
- bool supports_create_;
- ScopedVector<std::string> primary_mimetypes_;
- ScopedVector<std::string> secondary_mimetypes_;
- ScopedVector<std::string> primary_extensions_;
- ScopedVector<std::string> secondary_extensions_;
- ScopedVector<Link> links_;
- ScopedVector<AppIcon> app_icons_;
-};
-
-// Account metadata feed represents the metadata object attached to the user's
-// account.
-class AccountMetadata {
- public:
- AccountMetadata();
- virtual ~AccountMetadata();
-
- // Creates feed from parsed JSON Value. You should call this
- // instead of instantiating JSONValueConverter by yourself because
- // this method does some post-process for some fields. See
- // FillRemainingFields comment and implementation in ResourceEntry
- // class for the details.
- static scoped_ptr<AccountMetadata> CreateFrom(const base::Value& value);
-
- int64 quota_bytes_total() const {
- return quota_bytes_total_;
- }
-
- int64 quota_bytes_used() const {
- return quota_bytes_used_;
- }
-
- int64 largest_changestamp() const {
- return largest_changestamp_;
- }
-
- const ScopedVector<InstalledApp>& installed_apps() const {
- return installed_apps_;
- }
-
- void set_quota_bytes_total(int64 quota_bytes_total) {
- quota_bytes_total_ = quota_bytes_total;
- }
- void set_quota_bytes_used(int64 quota_bytes_used) {
- quota_bytes_used_ = quota_bytes_used;
- }
- void set_largest_changestamp(int64 largest_changestamp) {
- largest_changestamp_ = largest_changestamp;
- }
- void set_installed_apps(ScopedVector<InstalledApp> installed_apps) {
- installed_apps_ = installed_apps.Pass();
- }
-
- // Registers the mapping between JSON field names and the members in
- // this class.
- static void RegisterJSONConverter(
- base::JSONValueConverter<AccountMetadata>* converter);
-
- private:
- // Parses and initializes data members from content of |value|.
- // Return false if parsing fails.
- bool Parse(const base::Value& value);
-
- int64 quota_bytes_total_;
- int64 quota_bytes_used_;
- int64 largest_changestamp_;
- ScopedVector<InstalledApp> installed_apps_;
-
- DISALLOW_COPY_AND_ASSIGN(AccountMetadata);
-};
-
-
} // namespace google_apis
#endif // GOOGLE_APIS_DRIVE_GDATA_WAPI_PARSER_H_
diff --git a/chromium/google_apis/drive/gdata_wapi_parser_unittest.cc b/chromium/google_apis/drive/gdata_wapi_parser_unittest.cc
index 58728d129ba..6655aed8cb0 100644
--- a/chromium/google_apis/drive/gdata_wapi_parser_unittest.cc
+++ b/chromium/google_apis/drive/gdata_wapi_parser_unittest.cc
@@ -17,8 +17,6 @@
namespace google_apis {
-// TODO(nhiroki): Move json files to out of 'chromeos' directory
-// (http://crbug.com/149788).
// Test document feed parsing.
TEST(GDataWAPIParserTest, ResourceListJsonParser) {
std::string error;
@@ -237,73 +235,6 @@ TEST(GDataWAPIParserTest, ResourceEntryJsonParser) {
EXPECT_EQ(-1, entry->image_rotation());
}
-TEST(GDataWAPIParserTest, AccountMetadataParser) {
- scoped_ptr<base::Value> document =
- test_util::LoadJSONFile("gdata/account_metadata.json");
- ASSERT_TRUE(document.get());
- base::DictionaryValue* document_dict = NULL;
- base::DictionaryValue* entry_value = NULL;
- ASSERT_TRUE(document->GetAsDictionary(&document_dict));
- ASSERT_TRUE(document_dict->GetDictionary(std::string("entry"), &entry_value));
- ASSERT_TRUE(entry_value);
-
- scoped_ptr<AccountMetadata> metadata(
- AccountMetadata::CreateFrom(*document));
- ASSERT_TRUE(metadata.get());
- EXPECT_EQ(GG_LONGLONG(6789012345), metadata->quota_bytes_used());
- EXPECT_EQ(GG_LONGLONG(9876543210), metadata->quota_bytes_total());
- EXPECT_EQ(654321, metadata->largest_changestamp());
- EXPECT_EQ(2U, metadata->installed_apps().size());
- const InstalledApp* first_app = metadata->installed_apps()[0];
- const InstalledApp* second_app = metadata->installed_apps()[1];
-
- ASSERT_TRUE(first_app);
- EXPECT_EQ("Drive App 1", first_app->app_name());
- EXPECT_EQ("Drive App Object 1", first_app->object_type());
- EXPECT_TRUE(first_app->supports_create());
- EXPECT_EQ("https://chrome.google.com/webstore/detail/abcdefabcdef",
- first_app->GetProductUrl().spec());
-
- ASSERT_EQ(2U, first_app->primary_mimetypes().size());
- EXPECT_EQ("application/test_type_1",
- *first_app->primary_mimetypes()[0]);
- EXPECT_EQ("application/vnd.google-apps.drive-sdk.11111111",
- *first_app->primary_mimetypes()[1]);
-
- ASSERT_EQ(1U, first_app->secondary_mimetypes().size());
- EXPECT_EQ("image/jpeg", *first_app->secondary_mimetypes()[0]);
-
- ASSERT_EQ(2U, first_app->primary_extensions().size());
- EXPECT_EQ("ext_1", *first_app->primary_extensions()[0]);
- EXPECT_EQ("ext_2", *first_app->primary_extensions()[1]);
-
- ASSERT_EQ(1U, first_app->secondary_extensions().size());
- EXPECT_EQ("ext_3", *first_app->secondary_extensions()[0]);
-
- ASSERT_EQ(1U, first_app->app_icons().size());
- EXPECT_EQ(AppIcon::ICON_DOCUMENT, first_app->app_icons()[0]->category());
- EXPECT_EQ(16, first_app->app_icons()[0]->icon_side_length());
- GURL icon_url = first_app->app_icons()[0]->GetIconURL();
- EXPECT_EQ("https://www.google.com/images/srpr/logo3w.png", icon_url.spec());
- InstalledApp::IconList icons =
- first_app->GetIconsForCategory(AppIcon::ICON_DOCUMENT);
- EXPECT_EQ("https://www.google.com/images/srpr/logo3w.png",
- icons[0].second.spec());
- icons = first_app->GetIconsForCategory(AppIcon::ICON_SHARED_DOCUMENT);
- EXPECT_TRUE(icons.empty());
-
- ASSERT_TRUE(second_app);
- EXPECT_EQ("Drive App 2", second_app->app_name());
- EXPECT_EQ("Drive App Object 2", second_app->object_type());
- EXPECT_EQ("https://chrome.google.com/webstore/detail/deadbeefdeadbeef",
- second_app->GetProductUrl().spec());
- EXPECT_FALSE(second_app->supports_create());
- EXPECT_EQ(2U, second_app->primary_mimetypes().size());
- EXPECT_EQ(0U, second_app->secondary_mimetypes().size());
- EXPECT_EQ(1U, second_app->primary_extensions().size());
- EXPECT_EQ(0U, second_app->secondary_extensions().size());
-}
-
TEST(GDataWAPIParserTest, ClassifyEntryKindByFileExtension) {
EXPECT_EQ(
ResourceEntry::KIND_OF_GOOGLE_DOCUMENT |
diff --git a/chromium/google_apis/drive/gdata_wapi_requests.cc b/chromium/google_apis/drive/gdata_wapi_requests.cc
index 1ae8a95ef08..42592172075 100644
--- a/chromium/google_apis/drive/gdata_wapi_requests.cc
+++ b/chromium/google_apis/drive/gdata_wapi_requests.cc
@@ -4,202 +4,10 @@
#include "google_apis/drive/gdata_wapi_requests.h"
-#include "base/location.h"
-#include "base/sequenced_task_runner.h"
-#include "base/task_runner_util.h"
-#include "base/values.h"
-#include "google_apis/drive/gdata_wapi_parser.h"
#include "google_apis/drive/gdata_wapi_url_generator.h"
-#include "google_apis/drive/request_sender.h"
-#include "google_apis/drive/request_util.h"
-#include "third_party/libxml/chromium/libxml_utils.h"
-
-using net::URLFetcher;
namespace google_apis {
-namespace {
-
-// Parses the JSON value to ResourceList.
-scoped_ptr<ResourceList> ParseResourceListOnBlockingPool(
- scoped_ptr<base::Value> value) {
- DCHECK(value);
-
- return ResourceList::ExtractAndParse(*value);
-}
-
-// Runs |callback| with |error| and |resource_list|, but replace the error code
-// with GDATA_PARSE_ERROR, if there was a parsing error.
-void DidParseResourceListOnBlockingPool(
- const GetResourceListCallback& callback,
- GDataErrorCode error,
- scoped_ptr<ResourceList> resource_list) {
- DCHECK(!callback.is_null());
-
- // resource_list being NULL indicates there was a parsing error.
- if (!resource_list)
- error = GDATA_PARSE_ERROR;
-
- callback.Run(error, resource_list.Pass());
-}
-
-// Parses the JSON value to ResourceList on the blocking pool and runs
-// |callback| on the UI thread once parsing is done.
-void ParseResourceListAndRun(
- scoped_refptr<base::TaskRunner> blocking_task_runner,
- const GetResourceListCallback& callback,
- GDataErrorCode error,
- scoped_ptr<base::Value> value) {
- DCHECK(!callback.is_null());
-
- if (!value) {
- callback.Run(error, scoped_ptr<ResourceList>());
- return;
- }
-
- base::PostTaskAndReplyWithResult(
- blocking_task_runner,
- FROM_HERE,
- base::Bind(&ParseResourceListOnBlockingPool, base::Passed(&value)),
- base::Bind(&DidParseResourceListOnBlockingPool, callback, error));
-}
-
-// Parses the JSON value to AccountMetadata and runs |callback| on the UI
-// thread once parsing is done.
-void ParseAccounetMetadataAndRun(const GetAccountMetadataCallback& callback,
- GDataErrorCode error,
- scoped_ptr<base::Value> value) {
- DCHECK(!callback.is_null());
-
- if (!value) {
- callback.Run(error, scoped_ptr<AccountMetadata>());
- return;
- }
-
- // Parsing AccountMetadata is cheap enough to do on UI thread.
- scoped_ptr<AccountMetadata> entry =
- google_apis::AccountMetadata::CreateFrom(*value);
- if (!entry) {
- callback.Run(GDATA_PARSE_ERROR, scoped_ptr<AccountMetadata>());
- return;
- }
-
- callback.Run(error, entry.Pass());
-}
-
-// Parses the |value| to ResourceEntry with error handling.
-// This is designed to be used for ResumeUploadRequest and
-// GetUploadStatusRequest.
-scoped_ptr<ResourceEntry> ParseResourceEntry(scoped_ptr<base::Value> value) {
- scoped_ptr<ResourceEntry> entry;
- if (value.get()) {
- entry = ResourceEntry::ExtractAndParse(*value);
-
- // Note: |value| may be NULL, in particular if the callback is for a
- // failure.
- if (!entry.get())
- LOG(WARNING) << "Invalid entry received on upload.";
- }
-
- return entry.Pass();
-}
-
-// Extracts the open link url from the JSON Feed. Used by AuthorizeApp().
-void ParseOpenLinkAndRun(const std::string& app_id,
- const AuthorizeAppCallback& callback,
- GDataErrorCode error,
- scoped_ptr<base::Value> value) {
- DCHECK(!callback.is_null());
-
- if (!value) {
- callback.Run(error, GURL());
- return;
- }
-
- // Parsing ResourceEntry is cheap enough to do on UI thread.
- scoped_ptr<ResourceEntry> resource_entry = ParseResourceEntry(value.Pass());
- if (!resource_entry) {
- callback.Run(GDATA_PARSE_ERROR, GURL());
- return;
- }
-
- // Look for the link to open the file with the app with |app_id|.
- const ScopedVector<Link>& resource_links = resource_entry->links();
- GURL open_link;
- for (size_t i = 0; i < resource_links.size(); ++i) {
- const Link& link = *resource_links[i];
- if (link.type() == Link::LINK_OPEN_WITH && link.app_id() == app_id) {
- open_link = link.href();
- break;
- }
- }
-
- if (open_link.is_empty())
- error = GDATA_OTHER_ERROR;
-
- callback.Run(error, open_link);
-}
-
-} // namespace
-
-//============================ GetResourceListRequest ========================
-
-GetResourceListRequest::GetResourceListRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GURL& override_url,
- int64 start_changestamp,
- const std::string& search_string,
- const std::string& directory_resource_id,
- const GetResourceListCallback& callback)
- : GetDataRequest(
- sender,
- base::Bind(&ParseResourceListAndRun,
- make_scoped_refptr(sender->blocking_task_runner()),
- callback)),
- url_generator_(url_generator),
- override_url_(override_url),
- start_changestamp_(start_changestamp),
- search_string_(search_string),
- directory_resource_id_(directory_resource_id) {
- DCHECK(!callback.is_null());
-}
-
-GetResourceListRequest::~GetResourceListRequest() {}
-
-GURL GetResourceListRequest::GetURL() const {
- return url_generator_.GenerateResourceListUrl(override_url_,
- start_changestamp_,
- search_string_,
- directory_resource_id_);
-}
-
-//============================ SearchByTitleRequest ==========================
-
-SearchByTitleRequest::SearchByTitleRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const std::string& title,
- const std::string& directory_resource_id,
- const GetResourceListCallback& callback)
- : GetDataRequest(
- sender,
- base::Bind(&ParseResourceListAndRun,
- make_scoped_refptr(sender->blocking_task_runner()),
- callback)),
- url_generator_(url_generator),
- title_(title),
- directory_resource_id_(directory_resource_id) {
- DCHECK(!callback.is_null());
-}
-
-SearchByTitleRequest::~SearchByTitleRequest() {}
-
-GURL SearchByTitleRequest::GetURL() const {
- return url_generator_.GenerateSearchByTitleUrl(
- title_, directory_resource_id_);
-}
-
//============================ GetResourceEntryRequest =======================
GetResourceEntryRequest::GetResourceEntryRequest(
@@ -222,459 +30,4 @@ GURL GetResourceEntryRequest::GetURL() const {
resource_id_, embed_origin_);
}
-//========================= GetAccountMetadataRequest ========================
-
-GetAccountMetadataRequest::GetAccountMetadataRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GetAccountMetadataCallback& callback,
- bool include_installed_apps)
- : GetDataRequest(sender,
- base::Bind(&ParseAccounetMetadataAndRun, callback)),
- url_generator_(url_generator),
- include_installed_apps_(include_installed_apps) {
- DCHECK(!callback.is_null());
-}
-
-GetAccountMetadataRequest::~GetAccountMetadataRequest() {}
-
-GURL GetAccountMetadataRequest::GetURL() const {
- return url_generator_.GenerateAccountMetadataUrl(include_installed_apps_);
-}
-
-//=========================== DeleteResourceRequest ==========================
-
-DeleteResourceRequest::DeleteResourceRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& resource_id,
- const std::string& etag)
- : EntryActionRequest(sender, callback),
- url_generator_(url_generator),
- resource_id_(resource_id),
- etag_(etag) {
- DCHECK(!callback.is_null());
-}
-
-DeleteResourceRequest::~DeleteResourceRequest() {}
-
-GURL DeleteResourceRequest::GetURL() const {
- return url_generator_.GenerateEditUrl(resource_id_);
-}
-
-URLFetcher::RequestType DeleteResourceRequest::GetRequestType() const {
- return URLFetcher::DELETE_REQUEST;
-}
-
-std::vector<std::string>
-DeleteResourceRequest::GetExtraRequestHeaders() const {
- std::vector<std::string> headers;
- headers.push_back(util::GenerateIfMatchHeader(etag_));
- return headers;
-}
-
-//========================== CreateDirectoryRequest ==========================
-
-CreateDirectoryRequest::CreateDirectoryRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GetDataCallback& callback,
- const std::string& parent_resource_id,
- const std::string& directory_title)
- : GetDataRequest(sender, callback),
- url_generator_(url_generator),
- parent_resource_id_(parent_resource_id),
- directory_title_(directory_title) {
- DCHECK(!callback.is_null());
-}
-
-CreateDirectoryRequest::~CreateDirectoryRequest() {}
-
-GURL CreateDirectoryRequest::GetURL() const {
- return url_generator_.GenerateContentUrl(parent_resource_id_);
-}
-
-URLFetcher::RequestType
-CreateDirectoryRequest::GetRequestType() const {
- return URLFetcher::POST;
-}
-
-bool CreateDirectoryRequest::GetContentData(std::string* upload_content_type,
- std::string* upload_content) {
- upload_content_type->assign("application/atom+xml");
- XmlWriter xml_writer;
- xml_writer.StartWriting();
- xml_writer.StartElement("entry");
- xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
-
- xml_writer.StartElement("category");
- xml_writer.AddAttribute("scheme",
- "http://schemas.google.com/g/2005#kind");
- xml_writer.AddAttribute("term",
- "http://schemas.google.com/docs/2007#folder");
- xml_writer.EndElement(); // Ends "category" element.
-
- xml_writer.WriteElement("title", directory_title_);
-
- xml_writer.EndElement(); // Ends "entry" element.
- xml_writer.StopWriting();
- upload_content->assign(xml_writer.GetWrittenString());
- DVLOG(1) << "CreateDirectory data: " << *upload_content_type << ", ["
- << *upload_content << "]";
- return true;
-}
-
-//=========================== RenameResourceRequest ==========================
-
-RenameResourceRequest::RenameResourceRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& resource_id,
- const std::string& new_title)
- : EntryActionRequest(sender, callback),
- url_generator_(url_generator),
- resource_id_(resource_id),
- new_title_(new_title) {
- DCHECK(!callback.is_null());
-}
-
-RenameResourceRequest::~RenameResourceRequest() {}
-
-URLFetcher::RequestType RenameResourceRequest::GetRequestType() const {
- return URLFetcher::PUT;
-}
-
-std::vector<std::string>
-RenameResourceRequest::GetExtraRequestHeaders() const {
- std::vector<std::string> headers;
- headers.push_back(util::kIfMatchAllHeader);
- return headers;
-}
-
-GURL RenameResourceRequest::GetURL() const {
- return url_generator_.GenerateEditUrl(resource_id_);
-}
-
-bool RenameResourceRequest::GetContentData(std::string* upload_content_type,
- std::string* upload_content) {
- upload_content_type->assign("application/atom+xml");
- XmlWriter xml_writer;
- xml_writer.StartWriting();
- xml_writer.StartElement("entry");
- xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
-
- xml_writer.WriteElement("title", new_title_);
-
- xml_writer.EndElement(); // Ends "entry" element.
- xml_writer.StopWriting();
- upload_content->assign(xml_writer.GetWrittenString());
- DVLOG(1) << "RenameResourceRequest data: " << *upload_content_type << ", ["
- << *upload_content << "]";
- return true;
-}
-
-//=========================== AuthorizeAppRequest ==========================
-
-AuthorizeAppRequest::AuthorizeAppRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const AuthorizeAppCallback& callback,
- const std::string& resource_id,
- const std::string& app_id)
- : GetDataRequest(sender,
- base::Bind(&ParseOpenLinkAndRun, app_id, callback)),
- url_generator_(url_generator),
- resource_id_(resource_id),
- app_id_(app_id) {
- DCHECK(!callback.is_null());
-}
-
-AuthorizeAppRequest::~AuthorizeAppRequest() {}
-
-URLFetcher::RequestType AuthorizeAppRequest::GetRequestType() const {
- return URLFetcher::PUT;
-}
-
-std::vector<std::string>
-AuthorizeAppRequest::GetExtraRequestHeaders() const {
- std::vector<std::string> headers;
- headers.push_back(util::kIfMatchAllHeader);
- return headers;
-}
-
-bool AuthorizeAppRequest::GetContentData(std::string* upload_content_type,
- std::string* upload_content) {
- upload_content_type->assign("application/atom+xml");
- XmlWriter xml_writer;
- xml_writer.StartWriting();
- xml_writer.StartElement("entry");
- xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
- xml_writer.AddAttribute("xmlns:docs", "http://schemas.google.com/docs/2007");
- xml_writer.WriteElement("docs:authorizedApp", app_id_);
-
- xml_writer.EndElement(); // Ends "entry" element.
- xml_writer.StopWriting();
- upload_content->assign(xml_writer.GetWrittenString());
- DVLOG(1) << "AuthorizeAppRequest data: " << *upload_content_type << ", ["
- << *upload_content << "]";
- return true;
-}
-
-GURL AuthorizeAppRequest::GetURL() const {
- return url_generator_.GenerateEditUrl(resource_id_);
-}
-
-//======================= AddResourceToDirectoryRequest ======================
-
-AddResourceToDirectoryRequest::AddResourceToDirectoryRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& parent_resource_id,
- const std::string& resource_id)
- : EntryActionRequest(sender, callback),
- url_generator_(url_generator),
- parent_resource_id_(parent_resource_id),
- resource_id_(resource_id) {
- DCHECK(!callback.is_null());
-}
-
-AddResourceToDirectoryRequest::~AddResourceToDirectoryRequest() {}
-
-GURL AddResourceToDirectoryRequest::GetURL() const {
- return url_generator_.GenerateContentUrl(parent_resource_id_);
-}
-
-URLFetcher::RequestType
-AddResourceToDirectoryRequest::GetRequestType() const {
- return URLFetcher::POST;
-}
-
-bool AddResourceToDirectoryRequest::GetContentData(
- std::string* upload_content_type, std::string* upload_content) {
- upload_content_type->assign("application/atom+xml");
- XmlWriter xml_writer;
- xml_writer.StartWriting();
- xml_writer.StartElement("entry");
- xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
-
- xml_writer.WriteElement(
- "id", url_generator_.GenerateEditUrlWithoutParams(resource_id_).spec());
-
- xml_writer.EndElement(); // Ends "entry" element.
- xml_writer.StopWriting();
- upload_content->assign(xml_writer.GetWrittenString());
- DVLOG(1) << "AddResourceToDirectoryRequest data: " << *upload_content_type
- << ", [" << *upload_content << "]";
- return true;
-}
-
-//==================== RemoveResourceFromDirectoryRequest ====================
-
-RemoveResourceFromDirectoryRequest::RemoveResourceFromDirectoryRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& parent_resource_id,
- const std::string& document_resource_id)
- : EntryActionRequest(sender, callback),
- url_generator_(url_generator),
- resource_id_(document_resource_id),
- parent_resource_id_(parent_resource_id) {
- DCHECK(!callback.is_null());
-}
-
-RemoveResourceFromDirectoryRequest::~RemoveResourceFromDirectoryRequest() {
-}
-
-GURL RemoveResourceFromDirectoryRequest::GetURL() const {
- return url_generator_.GenerateResourceUrlForRemoval(
- parent_resource_id_, resource_id_);
-}
-
-URLFetcher::RequestType
-RemoveResourceFromDirectoryRequest::GetRequestType() const {
- return URLFetcher::DELETE_REQUEST;
-}
-
-std::vector<std::string>
-RemoveResourceFromDirectoryRequest::GetExtraRequestHeaders() const {
- std::vector<std::string> headers;
- headers.push_back(util::kIfMatchAllHeader);
- return headers;
-}
-
-//======================= InitiateUploadNewFileRequest =======================
-
-InitiateUploadNewFileRequest::InitiateUploadNewFileRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const InitiateUploadCallback& callback,
- const std::string& content_type,
- int64 content_length,
- const std::string& parent_resource_id,
- const std::string& title)
- : InitiateUploadRequestBase(sender, callback, content_type, content_length),
- url_generator_(url_generator),
- parent_resource_id_(parent_resource_id),
- title_(title) {
-}
-
-InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() {}
-
-GURL InitiateUploadNewFileRequest::GetURL() const {
- return url_generator_.GenerateInitiateUploadNewFileUrl(parent_resource_id_);
-}
-
-net::URLFetcher::RequestType
-InitiateUploadNewFileRequest::GetRequestType() const {
- return net::URLFetcher::POST;
-}
-
-bool InitiateUploadNewFileRequest::GetContentData(
- std::string* upload_content_type,
- std::string* upload_content) {
- upload_content_type->assign("application/atom+xml");
- XmlWriter xml_writer;
- xml_writer.StartWriting();
- xml_writer.StartElement("entry");
- xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
- xml_writer.AddAttribute("xmlns:docs",
- "http://schemas.google.com/docs/2007");
- xml_writer.WriteElement("title", title_);
- xml_writer.EndElement(); // Ends "entry" element.
- xml_writer.StopWriting();
- upload_content->assign(xml_writer.GetWrittenString());
- DVLOG(1) << "InitiateUploadNewFile: " << *upload_content_type << ", ["
- << *upload_content << "]";
- return true;
-}
-
-//===================== InitiateUploadExistingFileRequest ====================
-
-InitiateUploadExistingFileRequest::InitiateUploadExistingFileRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const InitiateUploadCallback& callback,
- const std::string& content_type,
- int64 content_length,
- const std::string& resource_id,
- const std::string& etag)
- : InitiateUploadRequestBase(sender, callback, content_type, content_length),
- url_generator_(url_generator),
- resource_id_(resource_id),
- etag_(etag) {
-}
-
-InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() {}
-
-GURL InitiateUploadExistingFileRequest::GetURL() const {
- return url_generator_.GenerateInitiateUploadExistingFileUrl(resource_id_);
-}
-
-net::URLFetcher::RequestType
-InitiateUploadExistingFileRequest::GetRequestType() const {
- return net::URLFetcher::PUT;
-}
-
-bool InitiateUploadExistingFileRequest::GetContentData(
- std::string* upload_content_type,
- std::string* upload_content) {
- // According to the document there is no need to send the content-type.
- // However, the server would return 500 server error without the
- // content-type.
- // As its workaround, send "text/plain" content-type here.
- *upload_content_type = "text/plain";
- *upload_content = "";
- return true;
-}
-
-std::vector<std::string>
-InitiateUploadExistingFileRequest::GetExtraRequestHeaders() const {
- std::vector<std::string> headers(
- InitiateUploadRequestBase::GetExtraRequestHeaders());
- headers.push_back(util::GenerateIfMatchHeader(etag_));
- return headers;
-}
-
-//============================ ResumeUploadRequest ===========================
-
-ResumeUploadRequest::ResumeUploadRequest(
- RequestSender* sender,
- const UploadRangeCallback& callback,
- const ProgressCallback& progress_callback,
- const GURL& upload_location,
- int64 start_position,
- int64 end_position,
- int64 content_length,
- const std::string& content_type,
- const base::FilePath& local_file_path)
- : ResumeUploadRequestBase(sender,
- upload_location,
- start_position,
- end_position,
- content_length,
- content_type,
- local_file_path),
- callback_(callback),
- progress_callback_(progress_callback) {
- DCHECK(!callback_.is_null());
-}
-
-ResumeUploadRequest::~ResumeUploadRequest() {}
-
-void ResumeUploadRequest::OnRangeRequestComplete(
- const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
- callback_.Run(response, ParseResourceEntry(value.Pass()));
-}
-
-void ResumeUploadRequest::OnURLFetchUploadProgress(
- const URLFetcher* source, int64 current, int64 total) {
- if (!progress_callback_.is_null())
- progress_callback_.Run(current, total);
-}
-
-//========================== GetUploadStatusRequest ==========================
-
-GetUploadStatusRequest::GetUploadStatusRequest(
- RequestSender* sender,
- const UploadRangeCallback& callback,
- const GURL& upload_url,
- int64 content_length)
- : GetUploadStatusRequestBase(sender, upload_url, content_length),
- callback_(callback) {
- DCHECK(!callback.is_null());
-}
-
-GetUploadStatusRequest::~GetUploadStatusRequest() {}
-
-void GetUploadStatusRequest::OnRangeRequestComplete(
- const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
- callback_.Run(response, ParseResourceEntry(value.Pass()));
-}
-
-//========================== DownloadFileRequest ==========================
-
-DownloadFileRequest::DownloadFileRequest(
- RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const DownloadActionCallback& download_action_callback,
- const GetContentCallback& get_content_callback,
- const ProgressCallback& progress_callback,
- const std::string& resource_id,
- const base::FilePath& output_file_path)
- : DownloadFileRequestBase(
- sender,
- download_action_callback,
- get_content_callback,
- progress_callback,
- url_generator.GenerateDownloadFileUrl(resource_id),
- output_file_path) {
-}
-
-DownloadFileRequest::~DownloadFileRequest() {
-}
-
} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_wapi_requests.h b/chromium/google_apis/drive/gdata_wapi_requests.h
index a4587d7628b..7e64906ca48 100644
--- a/chromium/google_apis/drive/gdata_wapi_requests.h
+++ b/chromium/google_apis/drive/gdata_wapi_requests.h
@@ -6,96 +6,12 @@
#define GOOGLE_APIS_DRIVE_GDATA_WAPI_REQUESTS_H_
#include <string>
-#include <vector>
#include "google_apis/drive/base_requests.h"
-#include "google_apis/drive/drive_common_callbacks.h"
#include "google_apis/drive/gdata_wapi_url_generator.h"
namespace google_apis {
-class AccountMetadata;
-class GDataWapiUrlGenerator;
-class ResourceEntry;
-
-//============================ GetResourceListRequest ========================
-
-// This class performs the request for fetching a resource list.
-class GetResourceListRequest : public GetDataRequest {
- public:
- // override_url:
- // If empty, a hard-coded base URL of the WAPI server is used to fetch
- // the first page of the feed. This parameter is used for fetching 2nd
- // page and onward.
- //
- // start_changestamp:
- // This parameter specifies the starting point of a delta feed or 0 if a
- // full feed is necessary.
- //
- // search_string:
- // If non-empty, fetches a list of resources that match the search
- // string.
- //
- // directory_resource_id:
- // If non-empty, fetches a list of resources in a particular directory.
- //
- // callback:
- // Called once the feed is fetched. Must not be null.
- GetResourceListRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GURL& override_url,
- int64 start_changestamp,
- const std::string& search_string,
- const std::string& directory_resource_id,
- const GetResourceListCallback& callback);
- virtual ~GetResourceListRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const GURL override_url_;
- const int64 start_changestamp_;
- const std::string search_string_;
- const std::string directory_resource_id_;
-
- DISALLOW_COPY_AND_ASSIGN(GetResourceListRequest);
-};
-
-//============================ SearchByTitleRequest ==========================
-
-// This class performs the request for searching resources by title.
-class SearchByTitleRequest : public GetDataRequest {
- public:
- // title: the search query.
- //
- // directory_resource_id: If given (non-empty), the search target is
- // directly under the directory with the |directory_resource_id|.
- // If empty, the search target is all the existing resources.
- //
- // callback:
- // Called once the feed is fetched. Must not be null.
- SearchByTitleRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const std::string& title,
- const std::string& directory_resource_id,
- const GetResourceListCallback& callback);
- virtual ~SearchByTitleRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string title_;
- const std::string directory_resource_id_;
-
- DISALLOW_COPY_AND_ASSIGN(SearchByTitleRequest);
-};
-
//========================= GetResourceEntryRequest ==========================
// This class performs the request for fetching a single resource entry.
@@ -123,364 +39,6 @@ class GetResourceEntryRequest : public GetDataRequest {
DISALLOW_COPY_AND_ASSIGN(GetResourceEntryRequest);
};
-//========================= GetAccountMetadataRequest ========================
-
-// Callback used for GetAccountMetadata().
-typedef base::Callback<void(GDataErrorCode error,
- scoped_ptr<AccountMetadata> account_metadata)>
- GetAccountMetadataCallback;
-
-// This class performs the request for fetching account metadata.
-class GetAccountMetadataRequest : public GetDataRequest {
- public:
- // If |include_installed_apps| is set to true, the result should include
- // the list of installed third party applications.
- // |callback| must not be null.
- GetAccountMetadataRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GetAccountMetadataCallback& callback,
- bool include_installed_apps);
- virtual ~GetAccountMetadataRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const bool include_installed_apps_;
-
- DISALLOW_COPY_AND_ASSIGN(GetAccountMetadataRequest);
-};
-
-//=========================== DeleteResourceRequest ==========================
-
-// This class performs the request for deleting a resource.
-//
-// In WAPI, "gd:deleted" means that the resource was put in the trash, and
-// "docs:removed" means its permanently gone. Since what the class does is to
-// put the resource into trash, we have chosen "Delete" in the name, even though
-// we are preferring the term "Remove" in drive/google_api code.
-class DeleteResourceRequest : public EntryActionRequest {
- public:
- // |callback| must not be null.
- DeleteResourceRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& resource_id,
- const std::string& etag);
- virtual ~DeleteResourceRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string resource_id_;
- const std::string etag_;
-
- DISALLOW_COPY_AND_ASSIGN(DeleteResourceRequest);
-};
-
-//========================== CreateDirectoryRequest ==========================
-
-// This class performs the request for creating a directory.
-class CreateDirectoryRequest : public GetDataRequest {
- public:
- // A new directory will be created under a directory specified by
- // |parent_resource_id|. If this parameter is empty, a new directory will
- // be created in the root directory.
- // |callback| must not be null.
- CreateDirectoryRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const GetDataCallback& callback,
- const std::string& parent_resource_id,
- const std::string& directory_title);
- virtual ~CreateDirectoryRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string parent_resource_id_;
- const std::string directory_title_;
-
- DISALLOW_COPY_AND_ASSIGN(CreateDirectoryRequest);
-};
-
-//=========================== RenameResourceRequest ==========================
-
-// This class performs the request for renaming a document/file/directory.
-class RenameResourceRequest : public EntryActionRequest {
- public:
- // |callback| must not be null.
- RenameResourceRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& resource_id,
- const std::string& new_title);
- virtual ~RenameResourceRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
- virtual GURL GetURL() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string resource_id_;
- const std::string new_title_;
-
- DISALLOW_COPY_AND_ASSIGN(RenameResourceRequest);
-};
-
-//=========================== AuthorizeAppRequest ==========================
-
-// This class performs the request for authorizing an application specified
-// by |app_id| to access a document specified by |resource_id|.
-class AuthorizeAppRequest : public GetDataRequest {
- public:
- // |callback| must not be null.
- AuthorizeAppRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const AuthorizeAppCallback& callback,
- const std::string& resource_id,
- const std::string& app_id);
- virtual ~AuthorizeAppRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
- virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
- virtual GURL GetURL() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string resource_id_;
- const std::string app_id_;
-
- DISALLOW_COPY_AND_ASSIGN(AuthorizeAppRequest);
-};
-
-//======================= AddResourceToDirectoryRequest ======================
-
-// This class performs the request for adding a document/file/directory
-// to a directory.
-class AddResourceToDirectoryRequest : public EntryActionRequest {
- public:
- // |callback| must not be null.
- AddResourceToDirectoryRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& parent_resource_id,
- const std::string& resource_id);
- virtual ~AddResourceToDirectoryRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string parent_resource_id_;
- const std::string resource_id_;
-
- DISALLOW_COPY_AND_ASSIGN(AddResourceToDirectoryRequest);
-};
-
-//==================== RemoveResourceFromDirectoryRequest ====================
-
-// This class performs the request for removing a document/file/directory
-// from a directory.
-class RemoveResourceFromDirectoryRequest : public EntryActionRequest {
- public:
- // |callback| must not be null.
- RemoveResourceFromDirectoryRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const EntryActionCallback& callback,
- const std::string& parent_resource_id,
- const std::string& resource_id);
- virtual ~RemoveResourceFromDirectoryRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string resource_id_;
- const std::string parent_resource_id_;
-
- DISALLOW_COPY_AND_ASSIGN(RemoveResourceFromDirectoryRequest);
-};
-
-//======================= InitiateUploadNewFileRequest =======================
-
-// This class performs the request for initiating the upload of a new file.
-class InitiateUploadNewFileRequest : public InitiateUploadRequestBase {
- public:
- // |title| should be set.
- // |parent_upload_url| should be the upload_url() of the parent directory.
- // (resumable-create-media URL)
- // See also the comments of InitiateUploadRequestBase for more details
- // about the other parameters.
- InitiateUploadNewFileRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const InitiateUploadCallback& callback,
- const std::string& content_type,
- int64 content_length,
- const std::string& parent_resource_id,
- const std::string& title);
- virtual ~InitiateUploadNewFileRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string parent_resource_id_;
- const std::string title_;
-
- DISALLOW_COPY_AND_ASSIGN(InitiateUploadNewFileRequest);
-};
-
-//==================== InitiateUploadExistingFileRequest =====================
-
-// This class performs the request for initiating the upload of an existing
-// file.
-class InitiateUploadExistingFileRequest
- : public InitiateUploadRequestBase {
- public:
- // |upload_url| should be the upload_url() of the file
- // (resumable-create-media URL)
- // |etag| should be set if it is available to detect the upload confliction.
- // See also the comments of InitiateUploadRequestBase for more details
- // about the other parameters.
- InitiateUploadExistingFileRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const InitiateUploadCallback& callback,
- const std::string& content_type,
- int64 content_length,
- const std::string& resource_id,
- const std::string& etag);
- virtual ~InitiateUploadExistingFileRequest();
-
- protected:
- // UrlFetchRequestBase overrides.
- virtual GURL GetURL() const OVERRIDE;
- virtual net::URLFetcher::RequestType GetRequestType() const OVERRIDE;
- virtual std::vector<std::string> GetExtraRequestHeaders() const OVERRIDE;
- virtual bool GetContentData(std::string* upload_content_type,
- std::string* upload_content) OVERRIDE;
-
- private:
- const GDataWapiUrlGenerator url_generator_;
- const std::string resource_id_;
- const std::string etag_;
-
- DISALLOW_COPY_AND_ASSIGN(InitiateUploadExistingFileRequest);
-};
-
-//============================ ResumeUploadRequest ===========================
-
-// Performs the request for resuming the upload of a file.
-class ResumeUploadRequest : public ResumeUploadRequestBase {
- public:
- // See also ResumeUploadRequestBase's comment for parameters meaning.
- // |callback| must not be null.
- ResumeUploadRequest(RequestSender* sender,
- const UploadRangeCallback& callback,
- const ProgressCallback& progress_callback,
- const GURL& upload_location,
- int64 start_position,
- int64 end_position,
- int64 content_length,
- const std::string& content_type,
- const base::FilePath& local_file_path);
- virtual ~ResumeUploadRequest();
-
- protected:
- // UploadRangeRequestBase overrides.
- virtual void OnRangeRequestComplete(
- const UploadRangeResponse& response,
- scoped_ptr<base::Value> value) OVERRIDE;
- // content::UrlFetcherDelegate overrides.
- virtual void OnURLFetchUploadProgress(const net::URLFetcher* source,
- int64 current, int64 total) OVERRIDE;
-
- private:
- const UploadRangeCallback callback_;
- const ProgressCallback progress_callback_;
-
- DISALLOW_COPY_AND_ASSIGN(ResumeUploadRequest);
-};
-
-//========================== GetUploadStatusRequest ==========================
-
-// Performs the request to request the current upload status of a file.
-class GetUploadStatusRequest : public GetUploadStatusRequestBase {
- public:
- // See also GetUploadStatusRequestBase's comment for parameters meaning.
- // |callback| must not be null.
- GetUploadStatusRequest(RequestSender* sender,
- const UploadRangeCallback& callback,
- const GURL& upload_url,
- int64 content_length);
- virtual ~GetUploadStatusRequest();
-
- protected:
- // UploadRangeRequestBase overrides.
- virtual void OnRangeRequestComplete(
- const UploadRangeResponse& response,
- scoped_ptr<base::Value> value) OVERRIDE;
-
- private:
- const UploadRangeCallback callback_;
-
- DISALLOW_COPY_AND_ASSIGN(GetUploadStatusRequest);
-};
-
-
-//========================== DownloadFileRequest ==========================
-
-// This class performs the request for downloading of a specified file.
-class DownloadFileRequest : public DownloadFileRequestBase {
- public:
- // See also DownloadFileRequestBase's comment for parameters meaning.
- DownloadFileRequest(RequestSender* sender,
- const GDataWapiUrlGenerator& url_generator,
- const DownloadActionCallback& download_action_callback,
- const GetContentCallback& get_content_callback,
- const ProgressCallback& progress_callback,
- const std::string& resource_id,
- const base::FilePath& output_file_path);
- virtual ~DownloadFileRequest();
-
- DISALLOW_COPY_AND_ASSIGN(DownloadFileRequest);
-};
-
} // namespace google_apis
#endif // GOOGLE_APIS_DRIVE_GDATA_WAPI_REQUESTS_H_
diff --git a/chromium/google_apis/drive/gdata_wapi_requests_unittest.cc b/chromium/google_apis/drive/gdata_wapi_requests_unittest.cc
index e302f23f1e8..2c39745184a 100644
--- a/chromium/google_apis/drive/gdata_wapi_requests_unittest.cc
+++ b/chromium/google_apis/drive/gdata_wapi_requests_unittest.cc
@@ -2,19 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <algorithm>
-#include <map>
-
#include "base/bind.h"
-#include "base/file_util.h"
-#include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "google_apis/drive/dummy_auth_service.h"
#include "google_apis/drive/gdata_wapi_parser.h"
@@ -34,14 +24,9 @@ namespace google_apis {
namespace {
const char kTestUserAgent[] = "test-user-agent";
-const char kTestETag[] = "test_etag";
-const char kTestDownloadPathPrefix[] = "/download/";
class GDataWapiRequestsTest : public testing::Test {
public:
- GDataWapiRequestsTest() {
- }
-
virtual void SetUp() OVERRIDE {
request_context_getter_ = new net::TestURLRequestContextGetter(
message_loop_.message_loop_proxy());
@@ -51,35 +36,13 @@ class GDataWapiRequestsTest : public testing::Test {
message_loop_.message_loop_proxy(),
kTestUserAgent));
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-
ASSERT_TRUE(test_server_.InitializeAndWaitUntilReady());
test_server_.RegisterRequestHandler(
- base::Bind(&test_util::HandleDownloadFileRequest,
- test_server_.base_url(),
- base::Unretained(&http_request_)));
- test_server_.RegisterRequestHandler(
base::Bind(&GDataWapiRequestsTest::HandleResourceFeedRequest,
base::Unretained(this)));
- test_server_.RegisterRequestHandler(
- base::Bind(&GDataWapiRequestsTest::HandleMetadataRequest,
- base::Unretained(this)));
- test_server_.RegisterRequestHandler(
- base::Bind(&GDataWapiRequestsTest::HandleCreateSessionRequest,
- base::Unretained(this)));
- test_server_.RegisterRequestHandler(
- base::Bind(&GDataWapiRequestsTest::HandleUploadRequest,
- base::Unretained(this)));
- test_server_.RegisterRequestHandler(
- base::Bind(&GDataWapiRequestsTest::HandleDownloadRequest,
- base::Unretained(this)));
GURL test_base_url = test_util::GetBaseUrlForTesting(test_server_.port());
- url_generator_.reset(new GDataWapiUrlGenerator(
- test_base_url, test_base_url.Resolve(kTestDownloadPathPrefix)));
-
- received_bytes_ = 0;
- content_length_ = 0;
+ url_generator_.reset(new GDataWapiUrlGenerator(test_base_url));
}
protected:
@@ -90,16 +53,6 @@ class GDataWapiRequestsTest : public testing::Test {
const GURL absolute_url = test_server_.GetURL(request.relative_url);
std::string remaining_path;
- if (absolute_url.path() == "/feeds/default/private/full" &&
- request.method == net::test_server::METHOD_POST) {
- // This is a request for copying a document.
- // TODO(satorux): we should generate valid JSON data for the newly
- // copied document but for now, just return "file_entry.json"
- scoped_ptr<net::test_server::BasicHttpResponse> result(
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath("gdata/file_entry.json")));
- return result.PassAs<net::test_server::HttpResponse>();
- }
if (!test_util::RemovePrefix(absolute_url.path(),
"/feeds/default/private/full",
@@ -107,236 +60,34 @@ class GDataWapiRequestsTest : public testing::Test {
return scoped_ptr<net::test_server::HttpResponse>();
}
- if (remaining_path.empty()) {
- // Process the default feed.
+ // Process a feed for a single resource ID.
+ const std::string resource_id = net::UnescapeURLComponent(
+ remaining_path.substr(1), net::UnescapeRule::URL_SPECIAL_CHARS);
+ if (resource_id == "file:2_file_resource_id") {
scoped_ptr<net::test_server::BasicHttpResponse> result(
test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath("gdata/root_feed.json")));
+ test_util::GetTestFilePath("gdata/file_entry.json")));
return result.PassAs<net::test_server::HttpResponse>();
- } else {
- // Process a feed for a single resource ID.
- const std::string resource_id = net::UnescapeURLComponent(
- remaining_path.substr(1), net::UnescapeRule::URL_SPECIAL_CHARS);
- if (resource_id == "file:2_file_resource_id") {
- scoped_ptr<net::test_server::BasicHttpResponse> result(
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath("gdata/file_entry.json")));
- return result.PassAs<net::test_server::HttpResponse>();
- } else if (resource_id == "folder:root/contents" &&
- request.method == net::test_server::METHOD_POST) {
- // This is a request for creating a directory in the root directory.
- // TODO(satorux): we should generate valid JSON data for the newly
- // created directory but for now, just return "directory_entry.json"
- scoped_ptr<net::test_server::BasicHttpResponse> result(
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath(
- "gdata/directory_entry.json")));
- return result.PassAs<net::test_server::HttpResponse>();
- } else if (resource_id ==
- "folder:root/contents/file:2_file_resource_id" &&
- request.method == net::test_server::METHOD_DELETE) {
- // This is a request for deleting a file from the root directory.
- // TODO(satorux): Investigate what's returned from the server, and
- // copy it. For now, just return a random file, as the contents don't
- // matter.
+ } else if (resource_id == "invalid_resource_id") {
+ // Check if this is an authorization request for an app.
+ // This emulates to return invalid formatted result from the server.
+ if (request.method == net::test_server::METHOD_PUT &&
+ request.content.find("<docs:authorizedApp>") != std::string::npos) {
scoped_ptr<net::test_server::BasicHttpResponse> result(
test_util::CreateHttpResponseFromFile(
test_util::GetTestFilePath("gdata/testfile.txt")));
return result.PassAs<net::test_server::HttpResponse>();
- } else if (resource_id == "invalid_resource_id") {
- // Check if this is an authorization request for an app.
- // This emulates to return invalid formatted result from the server.
- if (request.method == net::test_server::METHOD_PUT &&
- request.content.find("<docs:authorizedApp>") != std::string::npos) {
- scoped_ptr<net::test_server::BasicHttpResponse> result(
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath("gdata/testfile.txt")));
- return result.PassAs<net::test_server::HttpResponse>();
- }
- }
- }
-
- return scoped_ptr<net::test_server::HttpResponse>();
- }
-
- // Handles a request for fetching a metadata feed.
- scoped_ptr<net::test_server::HttpResponse> HandleMetadataRequest(
- const net::test_server::HttpRequest& request) {
- http_request_ = request;
-
- const GURL absolute_url = test_server_.GetURL(request.relative_url);
- if (absolute_url.path() != "/feeds/metadata/default")
- return scoped_ptr<net::test_server::HttpResponse>();
-
- scoped_ptr<net::test_server::BasicHttpResponse> result(
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath(
- "gdata/account_metadata.json")));
- if (absolute_url.query().find("include-installed-apps=true") ==
- string::npos) {
- // Exclude the list of installed apps.
- scoped_ptr<base::Value> parsed_content(
- base::JSONReader::Read(result->content(), base::JSON_PARSE_RFC));
- CHECK(parsed_content);
-
- // Remove the install apps node.
- base::DictionaryValue* dictionary_value;
- CHECK(parsed_content->GetAsDictionary(&dictionary_value));
- dictionary_value->Remove("entry.docs$installedApp", NULL);
-
- // Write back it as the content of the result.
- std::string content;
- base::JSONWriter::Write(parsed_content.get(), &content);
- result->set_content(content);
- }
-
- return result.PassAs<net::test_server::HttpResponse>();
- }
-
- // Handles a request for creating a session for uploading.
- scoped_ptr<net::test_server::HttpResponse> HandleCreateSessionRequest(
- const net::test_server::HttpRequest& request) {
- http_request_ = request;
-
- const GURL absolute_url = test_server_.GetURL(request.relative_url);
- if (StartsWithASCII(absolute_url.path(),
- "/feeds/upload/create-session/default/private/full",
- true)) { // case sensitive
- // This is an initiating upload URL.
- scoped_ptr<net::test_server::BasicHttpResponse> http_response(
- new net::test_server::BasicHttpResponse);
-
- // Check an ETag.
- std::map<std::string, std::string>::const_iterator found =
- request.headers.find("If-Match");
- if (found != request.headers.end() &&
- found->second != "*" &&
- found->second != kTestETag) {
- http_response->set_code(net::HTTP_PRECONDITION_FAILED);
- return http_response.PassAs<net::test_server::HttpResponse>();
- }
-
- // Check if the X-Upload-Content-Length is present. If yes, store the
- // length of the file.
- found = request.headers.find("X-Upload-Content-Length");
- if (found == request.headers.end() ||
- !base::StringToInt64(found->second, &content_length_)) {
- return scoped_ptr<net::test_server::HttpResponse>();
- }
- received_bytes_ = 0;
-
- http_response->set_code(net::HTTP_OK);
- GURL upload_url;
- // POST is used for a new file, and PUT is used for an existing file.
- if (request.method == net::test_server::METHOD_POST) {
- upload_url = test_server_.GetURL("/upload_new_file");
- } else if (request.method == net::test_server::METHOD_PUT) {
- upload_url = test_server_.GetURL("/upload_existing_file");
- } else {
- return scoped_ptr<net::test_server::HttpResponse>();
}
- http_response->AddCustomHeader("Location", upload_url.spec());
- return http_response.PassAs<net::test_server::HttpResponse>();
}
return scoped_ptr<net::test_server::HttpResponse>();
}
- // Handles a request for uploading content.
- scoped_ptr<net::test_server::HttpResponse> HandleUploadRequest(
- const net::test_server::HttpRequest& request) {
- http_request_ = request;
-
- const GURL absolute_url = test_server_.GetURL(request.relative_url);
- if (absolute_url.path() != "/upload_new_file" &&
- absolute_url.path() != "/upload_existing_file") {
- return scoped_ptr<net::test_server::HttpResponse>();
- }
-
- // TODO(satorux): We should create a correct JSON data for the uploaded
- // file, but for now, just return file_entry.json.
- scoped_ptr<net::test_server::BasicHttpResponse> response =
- test_util::CreateHttpResponseFromFile(
- test_util::GetTestFilePath("gdata/file_entry.json"));
- // response.code() is set to SUCCESS. Change it to CREATED if it's a new
- // file.
- if (absolute_url.path() == "/upload_new_file")
- response->set_code(net::HTTP_CREATED);
-
- // Check if the Content-Range header is present. This must be present if
- // the request body is not empty.
- if (!request.content.empty()) {
- std::map<std::string, std::string>::const_iterator iter =
- request.headers.find("Content-Range");
- if (iter == request.headers.end())
- return scoped_ptr<net::test_server::HttpResponse>();
- int64 length = 0;
- int64 start_position = 0;
- int64 end_position = 0;
- if (!test_util::ParseContentRangeHeader(iter->second,
- &start_position,
- &end_position,
- &length)) {
- return scoped_ptr<net::test_server::HttpResponse>();
- }
- EXPECT_EQ(start_position, received_bytes_);
- EXPECT_EQ(length, content_length_);
- // end_position is inclusive, but so +1 to change the range to byte size.
- received_bytes_ = end_position + 1;
- }
-
- // Add Range header to the response, based on the values of
- // Content-Range header in the request.
- // The header is annotated only when at least one byte is received.
- if (received_bytes_ > 0) {
- response->AddCustomHeader(
- "Range",
- "bytes=0-" + base::Int64ToString(received_bytes_ - 1));
- }
-
- // Change the code to RESUME_INCOMPLETE if upload is not complete.
- if (received_bytes_ < content_length_)
- response->set_code(static_cast<net::HttpStatusCode>(308));
-
- return response.PassAs<net::test_server::HttpResponse>();
- }
-
- // Handles a request for downloading a file.
- scoped_ptr<net::test_server::HttpResponse> HandleDownloadRequest(
- const net::test_server::HttpRequest& request) {
- http_request_ = request;
-
- const GURL absolute_url = test_server_.GetURL(request.relative_url);
- std::string id;
- if (!test_util::RemovePrefix(absolute_url.path(),
- kTestDownloadPathPrefix,
- &id)) {
- return scoped_ptr<net::test_server::HttpResponse>();
- }
-
- // For testing, returns a text with |id| repeated 3 times.
- scoped_ptr<net::test_server::BasicHttpResponse> response(
- new net::test_server::BasicHttpResponse);
- response->set_code(net::HTTP_OK);
- response->set_content(id + id + id);
- response->set_content_type("text/plain");
- return response.PassAs<net::test_server::HttpResponse>();
- }
-
base::MessageLoopForIO message_loop_; // Test server needs IO thread.
net::test_server::EmbeddedTestServer test_server_;
scoped_ptr<RequestSender> request_sender_;
scoped_ptr<GDataWapiUrlGenerator> url_generator_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
- base::ScopedTempDir temp_dir_;
-
- // These fields are used to keep the current upload state during a
- // test case. These values are updated by the request from
- // ResumeUploadRequest, and used to construct the response for
- // both ResumeUploadRequest and GetUploadStatusRequest, to emulate
- // the WAPI server.
- int64 received_bytes_;
- int64 content_length_;
// The incoming HTTP request is saved so tests can verify the request
// parameters like HTTP method (ex. some requests should use DELETE
@@ -346,130 +97,6 @@ class GDataWapiRequestsTest : public testing::Test {
} // namespace
-TEST_F(GDataWapiRequestsTest, GetResourceListRequest_DefaultFeed) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<ResourceList> result_data;
-
- {
- base::RunLoop run_loop;
- GetResourceListRequest* request = new GetResourceListRequest(
- request_sender_.get(),
- *url_generator_,
- GURL(), // Pass an empty URL to use the default feed
- 0, // start changestamp
- std::string(), // search string
- std::string(), // directory resource ID
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)));
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full?v=3&alt=json&showroot=true&"
- "showfolders=true&include-shared=true&max-results=500",
- http_request_.relative_url);
-
- // Sanity check of the result.
- scoped_ptr<ResourceList> expected(
- ResourceList::ExtractAndParse(
- *test_util::LoadJSONFile("gdata/root_feed.json")));
- ASSERT_TRUE(result_data);
- EXPECT_EQ(expected->title(), result_data->title());
-}
-
-TEST_F(GDataWapiRequestsTest, GetResourceListRequest_ValidFeed) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<ResourceList> result_data;
-
- {
- base::RunLoop run_loop;
- GetResourceListRequest* request = new GetResourceListRequest(
- request_sender_.get(),
- *url_generator_,
- test_server_.GetURL("/files/gdata/root_feed.json"),
- 0, // start changestamp
- std::string(), // search string
- std::string(), // directory resource ID
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)));
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/files/gdata/root_feed.json?v=3&alt=json&showroot=true&"
- "showfolders=true&include-shared=true&max-results=500",
- http_request_.relative_url);
-
- scoped_ptr<ResourceList> expected(
- ResourceList::ExtractAndParse(
- *test_util::LoadJSONFile("gdata/root_feed.json")));
- ASSERT_TRUE(result_data);
- EXPECT_EQ(expected->title(), result_data->title());
-}
-
-TEST_F(GDataWapiRequestsTest, GetResourceListRequest_InvalidFeed) {
- // testfile.txt exists but the response is not JSON, so it should
- // emit a parse error instead.
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<ResourceList> result_data;
-
- {
- base::RunLoop run_loop;
- GetResourceListRequest* request = new GetResourceListRequest(
- request_sender_.get(),
- *url_generator_,
- test_server_.GetURL("/files/gdata/testfile.txt"),
- 0, // start changestamp
- std::string(), // search string
- std::string(), // directory resource ID
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)));
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(GDATA_PARSE_ERROR, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/files/gdata/testfile.txt?v=3&alt=json&showroot=true&"
- "showfolders=true&include-shared=true&max-results=500",
- http_request_.relative_url);
- EXPECT_FALSE(result_data);
-}
-
-TEST_F(GDataWapiRequestsTest, SearchByTitleRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<ResourceList> result_data;
-
- {
- base::RunLoop run_loop;
- SearchByTitleRequest* request = new SearchByTitleRequest(
- request_sender_.get(),
- *url_generator_,
- "search-title",
- std::string(), // directory resource id
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)));
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full?v=3&alt=json&showroot=true&"
- "showfolders=true&include-shared=true&max-results=500"
- "&title=search-title&title-exact=true",
- http_request_.relative_url);
- EXPECT_TRUE(result_data);
-}
-
TEST_F(GDataWapiRequestsTest, GetResourceEntryRequest_ValidResourceId) {
GDataErrorCode result_code = GDATA_OTHER_ERROR;
scoped_ptr<base::Value> result_data;
@@ -526,1036 +153,4 @@ TEST_F(GDataWapiRequestsTest, GetResourceEntryRequest_InvalidResourceId) {
ASSERT_FALSE(result_data);
}
-TEST_F(GDataWapiRequestsTest, GetAccountMetadataRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<AccountMetadata> result_data;
-
- {
- base::RunLoop run_loop;
- GetAccountMetadataRequest* request = new GetAccountMetadataRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- true); // Include installed apps.
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/feeds/metadata/default?v=3&alt=json&showroot=true"
- "&include-installed-apps=true",
- http_request_.relative_url);
-
- scoped_ptr<AccountMetadata> expected(
- AccountMetadata::CreateFrom(
- *test_util::LoadJSONFile("gdata/account_metadata.json")));
-
- ASSERT_TRUE(result_data.get());
- EXPECT_EQ(expected->largest_changestamp(),
- result_data->largest_changestamp());
- EXPECT_EQ(expected->quota_bytes_total(),
- result_data->quota_bytes_total());
- EXPECT_EQ(expected->quota_bytes_used(),
- result_data->quota_bytes_used());
-
- // Sanity check for installed apps.
- EXPECT_EQ(expected->installed_apps().size(),
- result_data->installed_apps().size());
-}
-
-TEST_F(GDataWapiRequestsTest,
- GetAccountMetadataRequestWithoutInstalledApps) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<AccountMetadata> result_data;
-
- {
- base::RunLoop run_loop;
- GetAccountMetadataRequest* request = new GetAccountMetadataRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- false); // Exclude installed apps.
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ("/feeds/metadata/default?v=3&alt=json&showroot=true",
- http_request_.relative_url);
-
- scoped_ptr<AccountMetadata> expected(
- AccountMetadata::CreateFrom(
- *test_util::LoadJSONFile("gdata/account_metadata.json")));
-
- ASSERT_TRUE(result_data.get());
- EXPECT_EQ(expected->largest_changestamp(),
- result_data->largest_changestamp());
- EXPECT_EQ(expected->quota_bytes_total(),
- result_data->quota_bytes_total());
- EXPECT_EQ(expected->quota_bytes_used(),
- result_data->quota_bytes_used());
-
- // Installed apps shouldn't be included.
- EXPECT_EQ(0U, result_data->installed_apps().size());
-}
-
-TEST_F(GDataWapiRequestsTest, DeleteResourceRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
-
- {
- base::RunLoop run_loop;
- DeleteResourceRequest* request = new DeleteResourceRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code)),
- "file:2_file_resource_id",
- std::string());
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method);
- EXPECT_EQ(
- "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json"
- "&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-}
-
-TEST_F(GDataWapiRequestsTest, DeleteResourceRequestWithETag) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
-
- {
- base::RunLoop run_loop;
- DeleteResourceRequest* request = new DeleteResourceRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code)),
- "file:2_file_resource_id",
- "etag");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method);
- EXPECT_EQ(
- "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json"
- "&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("etag", http_request_.headers["If-Match"]);
-}
-
-TEST_F(GDataWapiRequestsTest, CreateDirectoryRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- scoped_ptr<base::Value> result_data;
-
- // Create "new directory" in the root directory.
- {
- base::RunLoop run_loop;
- CreateDirectoryRequest* request = new CreateDirectoryRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- "folder:root",
- "new directory");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents?v=3&alt=json"
- "&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n"
- " <category scheme=\"http://schemas.google.com/g/2005#kind\" "
- "term=\"http://schemas.google.com/docs/2007#folder\"/>\n"
- " <title>new directory</title>\n"
- "</entry>\n",
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, RenameResourceRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
-
- // Rename a file with a new name "New File".
- {
- base::RunLoop run_loop;
- RenameResourceRequest* request = new RenameResourceRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code)),
- "file:2_file_resource_id",
- "New File");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- EXPECT_EQ(
- "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json"
- "&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n"
- " <title>New File</title>\n"
- "</entry>\n",
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_ValidFeed) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL result_data;
-
- // Authorize an app with APP_ID to access to a document.
- {
- base::RunLoop run_loop;
- AuthorizeAppRequest* request = new AuthorizeAppRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- "file:2_file_resource_id",
- "the_app_id");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(GURL("https://entry1_open_with_link/"), result_data);
-
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/file%3A2_file_resource_id"
- "?v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <docs:authorizedApp>the_app_id</docs:authorizedApp>\n"
- "</entry>\n",
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_NotFound) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL result_data;
-
- // Authorize an app with APP_ID to access to a document.
- {
- base::RunLoop run_loop;
- AuthorizeAppRequest* request = new AuthorizeAppRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- "file:2_file_resource_id",
- "unauthorized_app_id");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(GDATA_OTHER_ERROR, result_code);
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/file%3A2_file_resource_id"
- "?v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <docs:authorizedApp>unauthorized_app_id</docs:authorizedApp>\n"
- "</entry>\n",
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_InvalidFeed) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL result_data;
-
- // Authorize an app with APP_ID to access to a document but an invalid feed.
- {
- base::RunLoop run_loop;
- AuthorizeAppRequest* request = new AuthorizeAppRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &result_data)),
- "invalid_resource_id",
- "APP_ID");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(GDATA_PARSE_ERROR, result_code);
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/invalid_resource_id"
- "?v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <docs:authorizedApp>APP_ID</docs:authorizedApp>\n"
- "</entry>\n",
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, AddResourceToDirectoryRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
-
- // Add a file to the root directory.
- {
- base::RunLoop run_loop;
- AddResourceToDirectoryRequest* request =
- new AddResourceToDirectoryRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code)),
- "folder:root",
- "file:2_file_resource_id");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents?v=3&alt=json"
- "&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(base::StringPrintf("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n"
- " <id>%sfeeds/default/private/full/"
- "file%%3A2_file_resource_id</id>\n"
- "</entry>\n",
- test_server_.base_url().spec().c_str()),
- http_request_.content);
-}
-
-TEST_F(GDataWapiRequestsTest, RemoveResourceFromDirectoryRequest) {
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
-
- // Remove a file from the root directory.
- {
- base::RunLoop run_loop;
- RemoveResourceFromDirectoryRequest* request =
- new RemoveResourceFromDirectoryRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code)),
- "folder:root",
- "file:2_file_resource_id");
-
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- // DELETE method should be used, without the body content.
- EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method);
- EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents/"
- "file%3A2_file_resource_id?v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
- EXPECT_FALSE(http_request_.has_content);
-}
-
-// This test exercises InitiateUploadNewFileRequest and
-// ResumeUploadRequest for a scenario of uploading a new file.
-TEST_F(GDataWapiRequestsTest, UploadNewFile) {
- const std::string kUploadContent = "hello";
- const base::FilePath kTestFilePath =
- temp_dir_.path().AppendASCII("upload_file.txt");
- ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent));
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- // 1) Get the upload URL for uploading a new file.
- {
- base::RunLoop run_loop;
- InitiateUploadNewFileRequest* initiate_request =
- new InitiateUploadNewFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "folder:id",
- "New file");
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url);
- EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ(
- "/feeds/upload/create-session/default/private/full/folder%3Aid/contents"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <title>New file</title>\n"
- "</entry>\n",
- http_request_.content);
-
- // 2) Upload the content to the upload URL.
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- {
- base::RunLoop run_loop;
- ResumeUploadRequest* resume_request = new ResumeUploadRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- ProgressCallback(),
- upload_url,
- 0, // start_position
- kUploadContent.size(), // end_position (exclusive)
- kUploadContent.size(), // content_length,
- "text/plain", // content_type
- kTestFilePath);
-
- request_sender_->StartRequestWithRetry(resume_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes 0-" +
- base::Int64ToString(kUploadContent.size() -1) + "/" +
- base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- // The upload content should be set in the HTTP request.
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(kUploadContent, http_request_.content);
-
- // Check the response.
- EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file
- // The start and end positions should be set to -1, if an upload is complete.
- EXPECT_EQ(-1, response.start_position_received);
- EXPECT_EQ(-1, response.end_position_received);
-}
-
-// This test exercises InitiateUploadNewFileRequest and ResumeUploadRequest
-// for a scenario of uploading a new *large* file, which requires multiple
-// requests of ResumeUploadRequest. GetUploadRequest is also tested in this
-// test case.
-TEST_F(GDataWapiRequestsTest, UploadNewLargeFile) {
- const size_t kMaxNumBytes = 10;
- // This is big enough to cause multiple requests of ResumeUploadRequest
- // as we are going to send at most kMaxNumBytes at a time.
- // So, sending "kMaxNumBytes * 2 + 1" bytes ensures three
- // ResumeUploadRequests, which are start, middle and last requests.
- const std::string kUploadContent(kMaxNumBytes * 2 + 1, 'a');
- const base::FilePath kTestFilePath =
- temp_dir_.path().AppendASCII("upload_file.txt");
- ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent));
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- // 1) Get the upload URL for uploading a new file.
- {
- base::RunLoop run_loop;
- InitiateUploadNewFileRequest* initiate_request =
- new InitiateUploadNewFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "folder:id",
- "New file");
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url);
- EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ(
- "/feeds/upload/create-session/default/private/full/folder%3Aid/contents"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <title>New file</title>\n"
- "</entry>\n",
- http_request_.content);
-
- // 2) Before sending any data, check the current status.
- // This is an edge case test for GetUploadStatusRequest
- // (UploadRangeRequestBase).
- {
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- // Check the response by GetUploadStatusRequest.
- {
- base::RunLoop run_loop;
- GetUploadStatusRequest* get_upload_status_request =
- new GetUploadStatusRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- upload_url,
- kUploadContent.size());
- request_sender_->StartRequestWithRetry(get_upload_status_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes */" + base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_TRUE(http_request_.content.empty());
-
- // Check the response.
- EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
- EXPECT_EQ(0, response.start_position_received);
- EXPECT_EQ(0, response.end_position_received);
- }
-
- // 3) Upload the content to the upload URL with multiple requests.
- size_t num_bytes_consumed = 0;
- for (size_t start_position = 0; start_position < kUploadContent.size();
- start_position += kMaxNumBytes) {
- SCOPED_TRACE(testing::Message("start_position: ") << start_position);
-
- // The payload is at most kMaxNumBytes.
- const size_t remaining_size = kUploadContent.size() - start_position;
- const std::string payload = kUploadContent.substr(
- start_position, std::min(kMaxNumBytes, remaining_size));
- num_bytes_consumed += payload.size();
- // The end position is exclusive.
- const size_t end_position = start_position + payload.size();
-
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- {
- base::RunLoop run_loop;
- ResumeUploadRequest* resume_request = new ResumeUploadRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- ProgressCallback(),
- upload_url,
- start_position,
- end_position,
- kUploadContent.size(), // content_length,
- "text/plain", // content_type
- kTestFilePath);
- request_sender_->StartRequestWithRetry(resume_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes " +
- base::Int64ToString(start_position) + "-" +
- base::Int64ToString(end_position - 1) + "/" +
- base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- // The upload content should be set in the HTTP request.
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(payload, http_request_.content);
-
- // Check the response.
- if (payload.size() == remaining_size) {
- EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file.
- // The start and end positions should be set to -1, if an upload is
- // complete.
- EXPECT_EQ(-1, response.start_position_received);
- EXPECT_EQ(-1, response.end_position_received);
- // The upload process is completed, so exit from the loop.
- break;
- }
-
- EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
- EXPECT_EQ(0, response.start_position_received);
- EXPECT_EQ(static_cast<int64>(end_position),
- response.end_position_received);
-
- // Check the response by GetUploadStatusRequest.
- {
- base::RunLoop run_loop;
- GetUploadStatusRequest* get_upload_status_request =
- new GetUploadStatusRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- upload_url,
- kUploadContent.size());
- request_sender_->StartRequestWithRetry(get_upload_status_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes */" + base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_TRUE(http_request_.content.empty());
-
- // Check the response.
- EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
- EXPECT_EQ(0, response.start_position_received);
- EXPECT_EQ(static_cast<int64>(end_position),
- response.end_position_received);
- }
-
- EXPECT_EQ(kUploadContent.size(), num_bytes_consumed);
-}
-
-// This test exercises InitiateUploadNewFileRequest and ResumeUploadRequest
-// for a scenario of uploading a new *empty* file.
-//
-// The test is almost identical to UploadNewFile. The only difference is the
-// expectation for the Content-Range header.
-TEST_F(GDataWapiRequestsTest, UploadNewEmptyFile) {
- const std::string kUploadContent;
- const base::FilePath kTestFilePath =
- temp_dir_.path().AppendASCII("empty_file.txt");
- ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent));
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- // 1) Get the upload URL for uploading a new file.
- {
- base::RunLoop run_loop;
- InitiateUploadNewFileRequest* initiate_request =
- new InitiateUploadNewFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "folder:id",
- "New file");
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url);
- EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ(
- "/feeds/upload/create-session/default/private/full/folder%3Aid/contents"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
-
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("<?xml version=\"1.0\"?>\n"
- "<entry xmlns=\"http://www.w3.org/2005/Atom\" "
- "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n"
- " <title>New file</title>\n"
- "</entry>\n",
- http_request_.content);
-
- // 2) Upload the content to the upload URL.
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- {
- base::RunLoop run_loop;
- ResumeUploadRequest* resume_request = new ResumeUploadRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- ProgressCallback(),
- upload_url,
- 0, // start_position
- kUploadContent.size(), // end_position (exclusive)
- kUploadContent.size(), // content_length,
- "text/plain", // content_type
- kTestFilePath);
- request_sender_->StartRequestWithRetry(resume_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should not exit if the content is empty.
- // We should not generate the header with an invalid value "bytes 0--1/0".
- EXPECT_EQ(0U, http_request_.headers.count("Content-Range"));
- // The upload content should be set in the HTTP request.
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(kUploadContent, http_request_.content);
-
- // Check the response.
- EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file.
- // The start and end positions should be set to -1, if an upload is complete.
- EXPECT_EQ(-1, response.start_position_received);
- EXPECT_EQ(-1, response.end_position_received);
-}
-
-// This test exercises InitiateUploadExistingFileRequest and
-// ResumeUploadRequest for a scenario of updating an existing file.
-TEST_F(GDataWapiRequestsTest, UploadExistingFile) {
- const std::string kUploadContent = "hello";
- const base::FilePath kTestFilePath =
- temp_dir_.path().AppendASCII("upload_file.txt");
- ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent));
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- // 1) Get the upload URL for uploading an existing file.
- {
- base::RunLoop run_loop;
- InitiateUploadExistingFileRequest* initiate_request =
- new InitiateUploadExistingFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "file:foo",
- std::string() /* etag */);
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(test_server_.GetURL("/upload_existing_file"), upload_url);
- // For updating an existing file, METHOD_PUT should be used.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- // Even though the body is empty, the content type should be set to
- // "text/plain".
- EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
- // For updating an existing file, an empty body should be attached (PUT
- // requires a body)
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("", http_request_.content);
- EXPECT_EQ("*", http_request_.headers["If-Match"]);
-
- // 2) Upload the content to the upload URL.
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- {
- base::RunLoop run_loop;
- ResumeUploadRequest* resume_request = new ResumeUploadRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- ProgressCallback(),
- upload_url,
- 0, // start_position
- kUploadContent.size(), // end_position (exclusive)
- kUploadContent.size(), // content_length,
- "text/plain", // content_type
- kTestFilePath);
-
- request_sender_->StartRequestWithRetry(resume_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes 0-" +
- base::Int64ToString(kUploadContent.size() -1) + "/" +
- base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- // The upload content should be set in the HTTP request.
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(kUploadContent, http_request_.content);
-
- // Check the response.
- EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file.
- // The start and end positions should be set to -1, if an upload is complete.
- EXPECT_EQ(-1, response.start_position_received);
- EXPECT_EQ(-1, response.end_position_received);
-}
-
-// This test exercises InitiateUploadExistingFileRequest and
-// ResumeUploadRequest for a scenario of updating an existing file.
-TEST_F(GDataWapiRequestsTest, UploadExistingFileWithETag) {
- const std::string kUploadContent = "hello";
- const base::FilePath kTestFilePath =
- temp_dir_.path().AppendASCII("upload_file.txt");
- ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent));
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- // 1) Get the upload URL for uploading an existing file.
- {
- base::RunLoop run_loop;
- InitiateUploadExistingFileRequest* initiate_request =
- new InitiateUploadExistingFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "file:foo",
- kTestETag);
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(test_server_.GetURL("/upload_existing_file"), upload_url);
- // For updating an existing file, METHOD_PUT should be used.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- // Even though the body is empty, the content type should be set to
- // "text/plain".
- EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
- // For updating an existing file, an empty body should be attached (PUT
- // requires a body)
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("", http_request_.content);
- EXPECT_EQ(kTestETag, http_request_.headers["If-Match"]);
-
- // 2) Upload the content to the upload URL.
- UploadRangeResponse response;
- scoped_ptr<ResourceEntry> new_entry;
-
- {
- base::RunLoop run_loop;
- ResumeUploadRequest* resume_request = new ResumeUploadRequest(
- request_sender_.get(),
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&response, &new_entry)),
- ProgressCallback(),
- upload_url,
- 0, // start_position
- kUploadContent.size(), // end_position (exclusive)
- kUploadContent.size(), // content_length,
- "text/plain", // content_type
- kTestFilePath);
- request_sender_->StartRequestWithRetry(resume_request);
- run_loop.Run();
- }
-
- // METHOD_PUT should be used to upload data.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // Request should go to the upload URL.
- EXPECT_EQ(upload_url.path(), http_request_.relative_url);
- // Content-Range header should be added.
- EXPECT_EQ("bytes 0-" +
- base::Int64ToString(kUploadContent.size() -1) + "/" +
- base::Int64ToString(kUploadContent.size()),
- http_request_.headers["Content-Range"]);
- // The upload content should be set in the HTTP request.
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ(kUploadContent, http_request_.content);
-
- // Check the response.
- EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file.
- // The start and end positions should be set to -1, if an upload is complete.
- EXPECT_EQ(-1, response.start_position_received);
- EXPECT_EQ(-1, response.end_position_received);
-}
-
-// This test exercises InitiateUploadExistingFileRequest for a scenario of
-// confliction on updating an existing file.
-TEST_F(GDataWapiRequestsTest, UploadExistingFileWithETagConflict) {
- const std::string kUploadContent = "hello";
- const std::string kWrongETag = "wrong_etag";
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- GURL upload_url;
-
- {
- base::RunLoop run_loop;
- InitiateUploadExistingFileRequest* initiate_request =
- new InitiateUploadExistingFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &upload_url)),
- "text/plain",
- kUploadContent.size(),
- "file:foo",
- kWrongETag);
- request_sender_->StartRequestWithRetry(initiate_request);
- run_loop.Run();
- }
-
- EXPECT_EQ(HTTP_PRECONDITION, result_code);
- // For updating an existing file, METHOD_PUT should be used.
- EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method);
- // convert=false should be passed as files should be uploaded as-is.
- EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo"
- "?convert=false&v=3&alt=json&showroot=true",
- http_request_.relative_url);
- // Even though the body is empty, the content type should be set to
- // "text/plain".
- EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]);
- EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kUploadContent.size()),
- http_request_.headers["X-Upload-Content-Length"]);
- // For updating an existing file, an empty body should be attached (PUT
- // requires a body)
- EXPECT_TRUE(http_request_.has_content);
- EXPECT_EQ("", http_request_.content);
- EXPECT_EQ(kWrongETag, http_request_.headers["If-Match"]);
-}
-
-TEST_F(GDataWapiRequestsTest, DownloadFileRequest) {
- const base::FilePath kDownloadedFilePath =
- temp_dir_.path().AppendASCII("cache_file");
- const std::string kTestIdWithTypeLabel("file:dummyId");
- const std::string kTestId("dummyId");
-
- GDataErrorCode result_code = GDATA_OTHER_ERROR;
- base::FilePath temp_file;
- {
- base::RunLoop run_loop;
- DownloadFileRequest* request = new DownloadFileRequest(
- request_sender_.get(),
- *url_generator_,
- test_util::CreateQuitCallback(
- &run_loop,
- test_util::CreateCopyResultCallback(&result_code, &temp_file)),
- GetContentCallback(),
- ProgressCallback(),
- kTestIdWithTypeLabel,
- kDownloadedFilePath);
- request_sender_->StartRequestWithRetry(request);
- run_loop.Run();
- }
-
- std::string contents;
- base::ReadFileToString(temp_file, &contents);
- base::DeleteFile(temp_file, false);
-
- EXPECT_EQ(HTTP_SUCCESS, result_code);
- EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
- EXPECT_EQ(kTestDownloadPathPrefix + kTestId, http_request_.relative_url);
- EXPECT_EQ(kDownloadedFilePath, temp_file);
-
- const std::string expected_contents = kTestId + kTestId + kTestId;
- EXPECT_EQ(expected_contents, contents);
-}
-
} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_wapi_url_generator.cc b/chromium/google_apis/drive/gdata_wapi_url_generator.cc
index c1263b71f34..ffdb0f3d943 100644
--- a/chromium/google_apis/drive/gdata_wapi_url_generator.cc
+++ b/chromium/google_apis/drive/gdata_wapi_url_generator.cc
@@ -14,52 +14,15 @@
namespace google_apis {
namespace {
-// Content URL for modification or resource list retrieval in a particular
-// directory specified by "%s" which will be replaced with its resource id.
-const char kContentURLFormat[] = "/feeds/default/private/full/%s/contents";
-
-// Content URL for removing a resource specified by the latter "%s" from the
-// directory specified by the former "%s".
-const char kResourceURLForRemovalFormat[] =
- "/feeds/default/private/full/%s/contents/%s";
-
// URL requesting single resource entry whose resource id is followed by this
// prefix.
const char kGetEditURLPrefix[] = "/feeds/default/private/full/";
-// Root resource list url.
-const char kResourceListRootURL[] = "/feeds/default/private/full";
-
-// Metadata feed with things like user quota.
-const char kAccountMetadataURL[] = "/feeds/metadata/default";
-
-// URL to upload a new file under a particular directory specified by "%s".
-const char kInitiateUploadNewFileURLFormat[] =
- "/feeds/upload/create-session/default/private/full/%s/contents";
-
-// URL to upload a file content to overwrite a file whose resource id is
-// followed by this prefix.
-const char kInitiateUploadExistingFileURLPrefix[] =
- "/feeds/upload/create-session/default/private/full/";
-
-// Maximum number of resource entries to include in a feed.
-// Be careful not to use something too small because it might overload the
-// server. Be careful not to use something too large because it makes the
-// "fetched N items" UI less responsive.
-const int kMaxDocumentsPerFeed = 500;
-const int kMaxDocumentsPerSearchFeed = 50;
-
-// URL requesting documents list of changes to documents collections.
-const char kGetChangesListURL[] = "/feeds/default/private/changes";
-
} // namespace
const char GDataWapiUrlGenerator::kBaseUrlForProduction[] =
"https://docs.google.com/";
-const char GDataWapiUrlGenerator::kBaseDownloadUrlForProduction[] =
- "https://www.googledrive.com/host/";
-
// static
GURL GDataWapiUrlGenerator::AddStandardUrlParams(const GURL& url) {
GURL result = net::AppendOrReplaceQueryParameter(url, "v", "3");
@@ -68,90 +31,13 @@ GURL GDataWapiUrlGenerator::AddStandardUrlParams(const GURL& url) {
return result;
}
-// static
-GURL GDataWapiUrlGenerator::AddInitiateUploadUrlParams(const GURL& url) {
- GURL result = net::AppendOrReplaceQueryParameter(url, "convert", "false");
- return AddStandardUrlParams(result);
-}
-
-// static
-GURL GDataWapiUrlGenerator::AddFeedUrlParams(
- const GURL& url,
- int num_items_to_fetch) {
- GURL result = AddStandardUrlParams(url);
- result = net::AppendOrReplaceQueryParameter(result, "showfolders", "true");
- result = net::AppendOrReplaceQueryParameter(result, "include-shared", "true");
- result = net::AppendOrReplaceQueryParameter(
- result, "max-results", base::IntToString(num_items_to_fetch));
- return result;
-}
-
-GDataWapiUrlGenerator::GDataWapiUrlGenerator(const GURL& base_url,
- const GURL& base_download_url)
- : base_url_(base_url),
- base_download_url_(base_download_url) {
+GDataWapiUrlGenerator::GDataWapiUrlGenerator(const GURL& base_url)
+ : base_url_(base_url) {
}
GDataWapiUrlGenerator::~GDataWapiUrlGenerator() {
}
-GURL GDataWapiUrlGenerator::GenerateResourceListUrl(
- const GURL& override_url,
- int64 start_changestamp,
- const std::string& search_string,
- const std::string& directory_resource_id) const {
- DCHECK_LE(0, start_changestamp);
-
- int max_docs = search_string.empty() ? kMaxDocumentsPerFeed :
- kMaxDocumentsPerSearchFeed;
- GURL url;
- if (!override_url.is_empty()) {
- // |override_url| specifies the URL of the continuation feed when the feed
- // is broken up to multiple chunks. In this case we must not add the
- // |start_changestamp| that provides the original start point.
- start_changestamp = 0;
- url = override_url;
- } else if (start_changestamp > 0) {
- // The start changestamp shouldn't be used for a search.
- DCHECK(search_string.empty());
- url = base_url_.Resolve(kGetChangesListURL);
- } else if (!directory_resource_id.empty()) {
- url = base_url_.Resolve(
- base::StringPrintf(kContentURLFormat,
- net::EscapePath(
- directory_resource_id).c_str()));
- } else {
- url = base_url_.Resolve(kResourceListRootURL);
- }
-
- url = AddFeedUrlParams(url, max_docs);
-
- if (start_changestamp) {
- url = net::AppendOrReplaceQueryParameter(
- url, "start-index", base::Int64ToString(start_changestamp));
- }
- if (!search_string.empty()) {
- url = net::AppendOrReplaceQueryParameter(url, "q", search_string);
- }
-
- return url;
-}
-
-GURL GDataWapiUrlGenerator::GenerateSearchByTitleUrl(
- const std::string& title,
- const std::string& directory_resource_id) const {
- DCHECK(!title.empty());
-
- GURL url = directory_resource_id.empty() ?
- base_url_.Resolve(kResourceListRootURL) :
- base_url_.Resolve(base::StringPrintf(
- kContentURLFormat, net::EscapePath(directory_resource_id).c_str()));
- url = AddFeedUrlParams(url, kMaxDocumentsPerFeed);
- url = net::AppendOrReplaceQueryParameter(url, "title", title);
- url = net::AppendOrReplaceQueryParameter(url, "title-exact", "true");
- return url;
-}
-
GURL GDataWapiUrlGenerator::GenerateEditUrl(
const std::string& resource_id) const {
return AddStandardUrlParams(GenerateEditUrlWithoutParams(resource_id));
@@ -180,71 +66,4 @@ GURL GDataWapiUrlGenerator::GenerateEditUrlWithEmbedOrigin(
return url;
}
-GURL GDataWapiUrlGenerator::GenerateContentUrl(
- const std::string& resource_id) const {
- if (resource_id.empty()) {
- // |resource_id| must not be empty. Return an empty GURL as an error.
- return GURL();
- }
-
- GURL result = base_url_.Resolve(
- base::StringPrintf(kContentURLFormat,
- net::EscapePath(resource_id).c_str()));
- return AddStandardUrlParams(result);
-}
-
-GURL GDataWapiUrlGenerator::GenerateResourceUrlForRemoval(
- const std::string& parent_resource_id,
- const std::string& resource_id) const {
- if (resource_id.empty() || parent_resource_id.empty()) {
- // Both |resource_id| and |parent_resource_id| must be non-empty.
- // Return an empty GURL as an error.
- return GURL();
- }
-
- GURL result = base_url_.Resolve(
- base::StringPrintf(kResourceURLForRemovalFormat,
- net::EscapePath(parent_resource_id).c_str(),
- net::EscapePath(resource_id).c_str()));
- return AddStandardUrlParams(result);
-}
-
-GURL GDataWapiUrlGenerator::GenerateInitiateUploadNewFileUrl(
- const std::string& parent_resource_id) const {
- GURL result = base_url_.Resolve(
- base::StringPrintf(kInitiateUploadNewFileURLFormat,
- net::EscapePath(parent_resource_id).c_str()));
- return AddInitiateUploadUrlParams(result);
-}
-
-GURL GDataWapiUrlGenerator::GenerateInitiateUploadExistingFileUrl(
- const std::string& resource_id) const {
- GURL result = base_url_.Resolve(
- kInitiateUploadExistingFileURLPrefix + net::EscapePath(resource_id));
- return AddInitiateUploadUrlParams(result);
-}
-
-GURL GDataWapiUrlGenerator::GenerateResourceListRootUrl() const {
- return AddStandardUrlParams(base_url_.Resolve(kResourceListRootURL));
-}
-
-GURL GDataWapiUrlGenerator::GenerateAccountMetadataUrl(
- bool include_installed_apps) const {
- GURL result = AddStandardUrlParams(base_url_.Resolve(kAccountMetadataURL));
- if (include_installed_apps) {
- result = net::AppendOrReplaceQueryParameter(
- result, "include-installed-apps", "true");
- }
- return result;
-}
-
-GURL GDataWapiUrlGenerator::GenerateDownloadFileUrl(
- const std::string& resource_id) const {
- // Strip the file type prefix before the colon character.
- size_t colon = resource_id.find(':');
- return base_download_url_.Resolve(net::EscapePath(
- colon == std::string::npos ? resource_id
- : resource_id.substr(colon + 1)));
-}
-
} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_wapi_url_generator.h b/chromium/google_apis/drive/gdata_wapi_url_generator.h
index 05b565d404b..c0486cfff14 100644
--- a/chromium/google_apis/drive/gdata_wapi_url_generator.h
+++ b/chromium/google_apis/drive/gdata_wapi_url_generator.h
@@ -17,8 +17,7 @@ namespace google_apis {
// for production, and the local server for testing.
class GDataWapiUrlGenerator {
public:
- // The
- GDataWapiUrlGenerator(const GURL& base_url, const GURL& base_download_url);
+ GDataWapiUrlGenerator(const GURL& base_url);
~GDataWapiUrlGenerator();
// The base URL for communicating with the WAPI server for production.
@@ -31,53 +30,6 @@ class GDataWapiUrlGenerator {
// show folders in the feed are added to document feed URLs.
static GURL AddStandardUrlParams(const GURL& url);
- // Adds additional parameters for initiate uploading as well as standard
- // url params (as AddStandardUrlParams above does).
- static GURL AddInitiateUploadUrlParams(const GURL& url);
-
- // Adds additional parameters for API version, output content type and to
- // show folders in the feed are added to document feed URLs.
- static GURL AddFeedUrlParams(const GURL& url,
- int num_items_to_fetch);
-
- // Generates a URL for getting the resource list feed.
- //
- // The parameters other than |search_string| are mutually exclusive.
- // If |override_url| is non-empty, other parameters are ignored. Or if
- // |override_url| is empty, others are not used. Besides, |search_string|
- // cannot be set together with |start_changestamp|.
- //
- // override_url:
- // By default, a hard-coded base URL of the WAPI server is used.
- // The base URL can be overridden by |override_url|.
- // This is used for handling continuation of feeds (2nd page and onward).
- //
- // start_changestamp
- // If |start_changestamp| is 0, URL for a full feed is generated.
- // If |start_changestamp| is non-zero, URL for a delta feed is generated.
- //
- // search_string
- // If |search_string| is non-empty, q=... parameter is added, and
- // max-results=... parameter is adjusted for a search.
- //
- // directory_resource_id:
- // If |directory_resource_id| is non-empty, a URL for fetching documents in
- // a particular directory is generated.
- //
- GURL GenerateResourceListUrl(
- const GURL& override_url,
- int64 start_changestamp,
- const std::string& search_string,
- const std::string& directory_resource_id) const;
-
- // Generates a URL for searching resources by title (exact-match).
- // |directory_resource_id| is optional parameter. When it is empty
- // all the existing resources are target of the search. Otherwise,
- // the search target is just under the directory with it.
- GURL GenerateSearchByTitleUrl(
- const std::string& title,
- const std::string& directory_resource_id) const;
-
// Generates a URL for getting or editing the resource entry of
// the given resource ID.
GURL GenerateEditUrl(const std::string& resource_id) const;
@@ -98,41 +50,8 @@ class GDataWapiUrlGenerator {
GURL GenerateEditUrlWithEmbedOrigin(const std::string& resource_id,
const GURL& embed_origin) const;
- // Generates a URL for editing the contents in the directory specified
- // by the given resource ID.
- GURL GenerateContentUrl(const std::string& resource_id) const;
-
- // Generates a URL to remove an entry specified by |resource_id| from
- // the directory specified by the given |parent_resource_id|.
- GURL GenerateResourceUrlForRemoval(const std::string& parent_resource_id,
- const std::string& resource_id) const;
-
- // Generates a URL to initiate uploading a new file to a directory
- // specified by |parent_resource_id|.
- GURL GenerateInitiateUploadNewFileUrl(
- const std::string& parent_resource_id) const;
-
- // Generates a URL to initiate uploading file content to overwrite a
- // file specified by |resource_id|.
- GURL GenerateInitiateUploadExistingFileUrl(
- const std::string& resource_id) const;
-
- // Generates a URL for getting the root resource list feed.
- // Used to make changes in the root directory (ex. create a directory in the
- // root directory)
- GURL GenerateResourceListRootUrl() const;
-
- // Generates a URL for getting the account metadata feed.
- // If |include_installed_apps| is set to true, the response will include the
- // list of installed third party applications.
- GURL GenerateAccountMetadataUrl(bool include_installed_apps) const;
-
- // Generates a URL for downloading a file.
- GURL GenerateDownloadFileUrl(const std::string& resource_id) const;
-
private:
const GURL base_url_;
- const GURL base_download_url_;
};
} // namespace google_apis
diff --git a/chromium/google_apis/drive/gdata_wapi_url_generator_unittest.cc b/chromium/google_apis/drive/gdata_wapi_url_generator_unittest.cc
index 63db63d6e3e..0ac8508d774 100644
--- a/chromium/google_apis/drive/gdata_wapi_url_generator_unittest.cc
+++ b/chromium/google_apis/drive/gdata_wapi_url_generator_unittest.cc
@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
+#include "url/url_util.h"
namespace google_apis {
@@ -13,8 +14,7 @@ class GDataWapiUrlGeneratorTest : public testing::Test {
public:
GDataWapiUrlGeneratorTest()
: url_generator_(
- GURL(GDataWapiUrlGenerator::kBaseUrlForProduction),
- GURL(GDataWapiUrlGenerator::kBaseDownloadUrlForProduction)) {
+ GURL(GDataWapiUrlGenerator::kBaseUrlForProduction)) {
}
protected:
@@ -27,113 +27,6 @@ TEST_F(GDataWapiUrlGeneratorTest, AddStandardUrlParams) {
GURL("http://www.example.com")).spec());
}
-TEST_F(GDataWapiUrlGeneratorTest, AddInitiateUploadUrlParams) {
- EXPECT_EQ("http://www.example.com/?convert=false&v=3&alt=json&showroot=true",
- GDataWapiUrlGenerator::AddInitiateUploadUrlParams(
- GURL("http://www.example.com")).spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, AddFeedUrlParams) {
- EXPECT_EQ(
- "http://www.example.com/?v=3&alt=json&showroot=true&"
- "showfolders=true"
- "&include-shared=true"
- "&max-results=100",
- GDataWapiUrlGenerator::AddFeedUrlParams(GURL("http://www.example.com"),
- 100 // num_items_to_fetch
- ).spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateResourceListUrl) {
- // This is the very basic URL for the GetResourceList request.
- EXPECT_EQ("https://docs.google.com/feeds/default/private/full"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500",
- url_generator_.GenerateResourceListUrl(
- GURL(), // override_url,
- 0, // start_changestamp,
- std::string(), // search_string,
- std::string() // directory resource ID
- ).spec());
-
- // With an override URL provided, the base URL is changed, but the default
- // parameters remain as-is.
- EXPECT_EQ("http://localhost/"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500",
- url_generator_.GenerateResourceListUrl(
- GURL("http://localhost/"), // override_url,
- 0, // start_changestamp,
- std::string(), // search_string,
- std::string() // directory resource ID
- ).spec());
-
- // With a non-zero start_changestamp provided, the base URL is changed from
- // "full" to "changes", and "start-index" parameter is added.
- EXPECT_EQ("https://docs.google.com/feeds/default/private/changes"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500&start-index=100",
- url_generator_.GenerateResourceListUrl(
- GURL(), // override_url,
- 100, // start_changestamp,
- std::string(), // search_string,
- std::string() // directory resource ID
- ).spec());
-
- // With a non-empty search string provided, "max-results" value is changed,
- // and "q" parameter is added.
- EXPECT_EQ("https://docs.google.com/feeds/default/private/full"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=50&q=foo",
- url_generator_.GenerateResourceListUrl(
- GURL(), // override_url,
- 0, // start_changestamp,
- "foo", // search_string,
- std::string() // directory resource ID
- ).spec());
-
- // With a non-empty directory resource ID provided, the base URL is
- // changed, but the default parameters remain.
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full/XXX/contents"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500",
- url_generator_.GenerateResourceListUrl(GURL(), // override_url,
- 0, // start_changestamp,
- std::string(), // search_string,
- "XXX" // directory resource ID
- ).spec());
-
- // With a non-empty override_url provided, the base URL is changed, but
- // the default parameters remain. Note that start-index should not be
- // overridden.
- EXPECT_EQ("http://example.com/"
- "?start-index=123&v=3&alt=json&showroot=true&showfolders=true"
- "&include-shared=true&max-results=500",
- url_generator_.GenerateResourceListUrl(
- GURL("http://example.com/?start-index=123"), // override_url,
- 100, // start_changestamp,
- std::string(), // search_string,
- "XXX" // directory resource ID
- ).spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateSearchByTitleUrl) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500&title=search-title&title-exact=true",
- url_generator_.GenerateSearchByTitleUrl(
- "search-title", std::string()).spec());
-
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full/XXX/contents"
- "?v=3&alt=json&showroot=true&showfolders=true&include-shared=true"
- "&max-results=500&title=search-title&title-exact=true",
- url_generator_.GenerateSearchByTitleUrl(
- "search-title", "XXX").spec());
-}
-
TEST_F(GDataWapiUrlGeneratorTest, GenerateEditUrl) {
EXPECT_EQ(
"https://docs.google.com/feeds/default/private/full/XXX?v=3&alt=json"
@@ -148,6 +41,8 @@ TEST_F(GDataWapiUrlGeneratorTest, GenerateEditUrlWithoutParams) {
}
TEST_F(GDataWapiUrlGeneratorTest, GenerateEditUrlWithEmbedOrigin) {
+ url::AddStandardScheme("chrome-extension");
+
EXPECT_EQ(
"https://docs.google.com/feeds/default/private/full/XXX?v=3&alt=json"
"&showroot=true&embedOrigin=chrome-extension%3A%2F%2Ftest",
@@ -162,61 +57,4 @@ TEST_F(GDataWapiUrlGeneratorTest, GenerateEditUrlWithEmbedOrigin) {
GURL()).spec());
}
-TEST_F(GDataWapiUrlGeneratorTest, GenerateContentUrl) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full/"
- "folder%3Aroot/contents?v=3&alt=json&showroot=true",
- url_generator_.GenerateContentUrl("folder:root").spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateResourceUrlForRemoval) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full/"
- "folder%3Aroot/contents/file%3AABCDE?v=3&alt=json&showroot=true",
- url_generator_.GenerateResourceUrlForRemoval(
- "folder:root", "file:ABCDE").spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateInitiateUploadNewFileUrl) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/upload/create-session/default/private/"
- "full/folder%3Aabcde/contents?convert=false&v=3&alt=json&showroot=true",
- url_generator_.GenerateInitiateUploadNewFileUrl("folder:abcde").spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateInitiateUploadExistingFileUrl) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/upload/create-session/default/private/"
- "full/file%3Aresource_id?convert=false&v=3&alt=json&showroot=true",
- url_generator_.GenerateInitiateUploadExistingFileUrl(
- "file:resource_id").spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateResourceListRootUrl) {
- EXPECT_EQ(
- "https://docs.google.com/feeds/default/private/full?v=3&alt=json"
- "&showroot=true",
- url_generator_.GenerateResourceListRootUrl().spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateAccountMetadataUrl) {
- // Include installed apps.
- EXPECT_EQ(
- "https://docs.google.com/feeds/metadata/default"
- "?v=3&alt=json&showroot=true&include-installed-apps=true",
- url_generator_.GenerateAccountMetadataUrl(true).spec());
-
- // Exclude installed apps.
- EXPECT_EQ(
- "https://docs.google.com/feeds/metadata/default?v=3&alt=json"
- "&showroot=true",
- url_generator_.GenerateAccountMetadataUrl(false).spec());
-}
-
-TEST_F(GDataWapiUrlGeneratorTest, GenerateDownloadFileUrl) {
- EXPECT_EQ(
- "https://www.googledrive.com/host/resourceId",
- url_generator_.GenerateDownloadFileUrl("file:resourceId").spec());
-}
-
} // namespace google_apis
diff --git a/chromium/google_apis/drive/task_util.cc b/chromium/google_apis/drive/task_util.cc
index 3f149e434ef..420afacb844 100644
--- a/chromium/google_apis/drive/task_util.cc
+++ b/chromium/google_apis/drive/task_util.cc
@@ -8,9 +8,9 @@
namespace google_apis {
-void RunTaskOnThread(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+void RunTaskOnThread(scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::Closure& task) {
- if (task_runner->BelongsToCurrentThread()) {
+ if (task_runner->RunsTasksOnCurrentThread()) {
task.Run();
} else {
const bool posted = task_runner->PostTask(FROM_HERE, task);
diff --git a/chromium/google_apis/drive/task_util.h b/chromium/google_apis/drive/task_util.h
index 443f719694e..3cba9bd2cf4 100644
--- a/chromium/google_apis/drive/task_util.h
+++ b/chromium/google_apis/drive/task_util.h
@@ -10,8 +10,8 @@
namespace google_apis {
-// Runs task on the thread to which |task_runner| belongs.
-void RunTaskOnThread(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+// Runs task on a thread on which |task_runner| may run tasks.
+void RunTaskOnThread(scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::Closure& task);
namespace internal {
@@ -95,17 +95,6 @@ struct ComposedCallback<void(T1, T2, T3, T4)> {
}
};
-// ComposedCallback with four arguments, and the second one is scoped_ptr.
-template<typename T1, typename T2, typename D2, typename T3, typename T4>
-struct ComposedCallback<void(T1, scoped_ptr<T2, D2>, T3, T4)> {
- static void Run(
- const base::Callback<void(const base::Closure&)>& runner,
- const base::Callback<void(T1, scoped_ptr<T2, D2>, T3, T4)>& callback,
- T1 arg1, scoped_ptr<T2, D2> arg2, T3 arg3, T4 arg4) {
- runner.Run(base::Bind(callback, arg1, base::Passed(&arg2), arg3, arg4));
- }
-};
-
} // namespace internal
// Returns callback that takes arguments (arg1, arg2, ...), create a closure
diff --git a/chromium/google_apis/drive/test_util.cc b/chromium/google_apis/drive/test_util.cc
index 20d8ad65a6f..e77d1cf69e8 100644
--- a/chromium/google_apis/drive/test_util.cc
+++ b/chromium/google_apis/drive/test_util.cc
@@ -57,7 +57,8 @@ void RunAndQuit(base::RunLoop* run_loop, const base::Closure& closure) {
bool WriteStringToFile(const base::FilePath& file_path,
const std::string& content) {
- int result = file_util::WriteFile(file_path, content.data(), content.size());
+ int result = base::WriteFile(
+ file_path, content.data(), static_cast<int>(content.size()));
return content.size() == static_cast<size_t>(result);
}
diff --git a/chromium/google_apis/gaia/DEPS b/chromium/google_apis/gaia/DEPS
deleted file mode 100644
index f445dee28bf..00000000000
--- a/chromium/google_apis/gaia/DEPS
+++ /dev/null
@@ -1,5 +0,0 @@
-specific_include_rules = {
- ".*_[a-z]*test\.cc": [
- "+content/public/test/test_browser_thread_bundle.h",
- ]
-}
diff --git a/chromium/google_apis/gaia/account_tracker.cc b/chromium/google_apis/gaia/account_tracker.cc
new file mode 100644
index 00000000000..65d28369be9
--- /dev/null
+++ b/chromium/google_apis/gaia/account_tracker.cc
@@ -0,0 +1,293 @@
+// Copyright 2014 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 "google_apis/gaia/account_tracker.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace gaia {
+
+AccountTracker::AccountTracker(
+ IdentityProvider* identity_provider,
+ net::URLRequestContextGetter* request_context_getter)
+ : identity_provider_(identity_provider),
+ request_context_getter_(request_context_getter),
+ shutdown_called_(false) {
+ identity_provider_->AddObserver(this);
+ identity_provider_->GetTokenService()->AddObserver(this);
+}
+
+AccountTracker::~AccountTracker() {
+ DCHECK(shutdown_called_);
+}
+
+void AccountTracker::Shutdown() {
+ shutdown_called_ = true;
+ STLDeleteValues(&user_info_requests_);
+ identity_provider_->GetTokenService()->RemoveObserver(this);
+ identity_provider_->RemoveObserver(this);
+}
+
+void AccountTracker::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void AccountTracker::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+std::vector<AccountIds> AccountTracker::GetAccounts() const {
+ const std::string active_account_id =
+ identity_provider_->GetActiveAccountId();
+ std::vector<AccountIds> accounts;
+
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ const AccountState& state = it->second;
+ bool is_visible = state.is_signed_in && !state.ids.gaia.empty();
+
+ if (it->first == active_account_id) {
+ if (is_visible)
+ accounts.insert(accounts.begin(), state.ids);
+ else
+ return std::vector<AccountIds>();
+
+ } else if (is_visible) {
+ accounts.push_back(state.ids);
+ }
+ }
+ return accounts;
+}
+
+AccountIds AccountTracker::FindAccountIdsByGaiaId(const std::string& gaia_id) {
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ const AccountState& state = it->second;
+ if (state.ids.gaia == gaia_id) {
+ return state.ids;
+ }
+ }
+
+ return AccountIds();
+}
+
+void AccountTracker::OnRefreshTokenAvailable(const std::string& account_id) {
+ // Ignore refresh tokens if there is no active account ID at all.
+ if (identity_provider_->GetActiveAccountId().empty())
+ return;
+
+ DVLOG(1) << "AVAILABLE " << account_id;
+ UpdateSignInState(account_id, true);
+}
+
+void AccountTracker::OnRefreshTokenRevoked(const std::string& account_id) {
+ DVLOG(1) << "REVOKED " << account_id;
+ UpdateSignInState(account_id, false);
+}
+
+void AccountTracker::OnActiveAccountLogin() {
+ std::vector<std::string> accounts =
+ identity_provider_->GetTokenService()->GetAccounts();
+
+ DVLOG(1) << "LOGIN " << accounts.size() << " accounts available.";
+
+ for (std::vector<std::string>::const_iterator it = accounts.begin();
+ it != accounts.end();
+ ++it) {
+ OnRefreshTokenAvailable(*it);
+ }
+}
+
+void AccountTracker::OnActiveAccountLogout() {
+ DVLOG(1) << "LOGOUT";
+ StopTrackingAllAccounts();
+}
+
+void AccountTracker::SetAccountStateForTest(AccountIds ids, bool is_signed_in) {
+ accounts_[ids.account_key].ids = ids;
+ accounts_[ids.account_key].is_signed_in = is_signed_in;
+
+ DVLOG(1) << "SetAccountStateForTest " << ids.account_key << ":"
+ << is_signed_in;
+
+ if (VLOG_IS_ON(1)) {
+ for (std::map<std::string, AccountState>::const_iterator it =
+ accounts_.begin();
+ it != accounts_.end();
+ ++it) {
+ DVLOG(1) << it->first << ":" << it->second.is_signed_in;
+ }
+ }
+}
+
+void AccountTracker::NotifyAccountAdded(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(
+ Observer, observer_list_, OnAccountAdded(account.ids));
+}
+
+void AccountTracker::NotifyAccountRemoved(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(
+ Observer, observer_list_, OnAccountRemoved(account.ids));
+}
+
+void AccountTracker::NotifySignInChanged(const AccountState& account) {
+ DCHECK(!account.ids.gaia.empty());
+ FOR_EACH_OBSERVER(Observer,
+ observer_list_,
+ OnAccountSignInChanged(account.ids, account.is_signed_in));
+}
+
+void AccountTracker::UpdateSignInState(const std::string account_key,
+ bool is_signed_in) {
+ StartTrackingAccount(account_key);
+ AccountState& account = accounts_[account_key];
+ bool needs_gaia_id = account.ids.gaia.empty();
+ bool was_signed_in = account.is_signed_in;
+ account.is_signed_in = is_signed_in;
+
+ if (needs_gaia_id && is_signed_in)
+ StartFetchingUserInfo(account_key);
+
+ if (!needs_gaia_id && (was_signed_in != is_signed_in))
+ NotifySignInChanged(account);
+}
+
+void AccountTracker::StartTrackingAccount(const std::string account_key) {
+ if (!ContainsKey(accounts_, account_key)) {
+ DVLOG(1) << "StartTracking " << account_key;
+ AccountState account_state;
+ account_state.ids.account_key = account_key;
+ account_state.ids.email = account_key;
+ account_state.is_signed_in = false;
+ accounts_.insert(make_pair(account_key, account_state));
+ }
+}
+
+void AccountTracker::StopTrackingAccount(const std::string account_key) {
+ DVLOG(1) << "StopTracking " << account_key;
+ if (ContainsKey(accounts_, account_key)) {
+ AccountState& account = accounts_[account_key];
+ if (!account.ids.gaia.empty()) {
+ UpdateSignInState(account_key, false);
+ NotifyAccountRemoved(account);
+ }
+ accounts_.erase(account_key);
+ }
+
+ if (ContainsKey(user_info_requests_, account_key))
+ DeleteFetcher(user_info_requests_[account_key]);
+}
+
+void AccountTracker::StopTrackingAllAccounts() {
+ while (!accounts_.empty())
+ StopTrackingAccount(accounts_.begin()->first);
+}
+
+void AccountTracker::StartFetchingUserInfo(const std::string account_key) {
+ if (ContainsKey(user_info_requests_, account_key))
+ DeleteFetcher(user_info_requests_[account_key]);
+
+ DVLOG(1) << "StartFetching " << account_key;
+ AccountIdFetcher* fetcher =
+ new AccountIdFetcher(identity_provider_->GetTokenService(),
+ request_context_getter_.get(),
+ this,
+ account_key);
+ user_info_requests_[account_key] = fetcher;
+ fetcher->Start();
+}
+
+void AccountTracker::OnUserInfoFetchSuccess(AccountIdFetcher* fetcher,
+ const std::string& gaia_id) {
+ const std::string& account_key = fetcher->account_key();
+ DCHECK(ContainsKey(accounts_, account_key));
+ AccountState& account = accounts_[account_key];
+
+ account.ids.gaia = gaia_id;
+ NotifyAccountAdded(account);
+
+ if (account.is_signed_in)
+ NotifySignInChanged(account);
+
+ DeleteFetcher(fetcher);
+}
+
+void AccountTracker::OnUserInfoFetchFailure(AccountIdFetcher* fetcher) {
+ LOG(WARNING) << "Failed to get UserInfo for " << fetcher->account_key();
+ std::string key = fetcher->account_key();
+ DeleteFetcher(fetcher);
+ StopTrackingAccount(key);
+}
+
+void AccountTracker::DeleteFetcher(AccountIdFetcher* fetcher) {
+ DVLOG(1) << "DeleteFetcher " << fetcher->account_key();
+ const std::string& account_key = fetcher->account_key();
+ DCHECK(ContainsKey(user_info_requests_, account_key));
+ DCHECK_EQ(fetcher, user_info_requests_[account_key]);
+ user_info_requests_.erase(account_key);
+ delete fetcher;
+}
+
+AccountIdFetcher::AccountIdFetcher(
+ OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context_getter,
+ AccountTracker* tracker,
+ const std::string& account_key)
+ : OAuth2TokenService::Consumer("gaia_account_tracker"),
+ token_service_(token_service),
+ request_context_getter_(request_context_getter),
+ tracker_(tracker),
+ account_key_(account_key) {
+}
+
+AccountIdFetcher::~AccountIdFetcher() {}
+
+void AccountIdFetcher::Start() {
+ login_token_request_ = token_service_->StartRequest(
+ account_key_, OAuth2TokenService::ScopeSet(), this);
+}
+
+void AccountIdFetcher::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ DCHECK_EQ(request, login_token_request_.get());
+
+ gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(request_context_getter_));
+
+ const int kMaxGetUserIdRetries = 3;
+ gaia_oauth_client_->GetUserId(access_token, kMaxGetUserIdRetries, this);
+}
+
+void AccountIdFetcher::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ LOG(ERROR) << "OnGetTokenFailure: " << error.ToString();
+ DCHECK_EQ(request, login_token_request_.get());
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+void AccountIdFetcher::OnGetUserIdResponse(const std::string& gaia_id) {
+ tracker_->OnUserInfoFetchSuccess(this, gaia_id);
+}
+
+void AccountIdFetcher::OnOAuthError() {
+ LOG(ERROR) << "OnOAuthError";
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+void AccountIdFetcher::OnNetworkError(int response_code) {
+ LOG(ERROR) << "OnNetworkError " << response_code;
+ tracker_->OnUserInfoFetchFailure(this);
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/account_tracker.h b/chromium/google_apis/gaia/account_tracker.h
new file mode 100644
index 00000000000..5a29ea62491
--- /dev/null
+++ b/chromium/google_apis/gaia/account_tracker.h
@@ -0,0 +1,149 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
+#define GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
+#include "google_apis/gaia/identity_provider.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+class GoogleServiceAuthError;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gaia {
+
+struct AccountIds {
+ std::string account_key; // The account ID used by OAuth2TokenService.
+ std::string gaia;
+ std::string email;
+};
+
+class AccountIdFetcher;
+
+// The AccountTracker keeps track of what accounts exist on the
+// profile and the state of their credentials. The tracker fetches the
+// gaia ID of each account it knows about.
+//
+// The AccountTracker maintains these invariants:
+// 1. Events are only fired after the gaia ID has been fetched.
+// 2. Add/Remove and SignIn/SignOut pairs are always generated in order.
+// 3. SignIn follows Add, and there will be a SignOut between SignIn & Remove.
+// 4. If there is no primary account, there are no other accounts.
+class AccountTracker : public OAuth2TokenService::Observer,
+ public IdentityProvider::Observer {
+ public:
+ AccountTracker(IdentityProvider* identity_provider,
+ net::URLRequestContextGetter* request_context_getter);
+ virtual ~AccountTracker();
+
+ class Observer {
+ public:
+ virtual void OnAccountAdded(const AccountIds& ids) = 0;
+ virtual void OnAccountRemoved(const AccountIds& ids) = 0;
+ virtual void OnAccountSignInChanged(const AccountIds& ids,
+ bool is_signed_in) = 0;
+ };
+
+ void Shutdown();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Returns the list of accounts that are signed in, and for which gaia IDs
+ // have been fetched. The primary account for the profile will be first
+ // in the vector. Additional accounts will be in order of their gaia IDs.
+ std::vector<AccountIds> GetAccounts() const;
+ AccountIds FindAccountIdsByGaiaId(const std::string& gaia_id);
+
+ // OAuth2TokenService::Observer implementation.
+ virtual void OnRefreshTokenAvailable(const std::string& account_key) OVERRIDE;
+ virtual void OnRefreshTokenRevoked(const std::string& account_key) OVERRIDE;
+
+ void OnUserInfoFetchSuccess(AccountIdFetcher* fetcher,
+ const std::string& gaia_id);
+ void OnUserInfoFetchFailure(AccountIdFetcher* fetcher);
+
+ // IdentityProvider::Observer implementation.
+ virtual void OnActiveAccountLogin() OVERRIDE;
+ virtual void OnActiveAccountLogout() OVERRIDE;
+
+ // Sets the state of an account. Does not fire notifications.
+ void SetAccountStateForTest(AccountIds ids, bool is_signed_in);
+
+ IdentityProvider* identity_provider() { return identity_provider_; }
+
+ private:
+ struct AccountState {
+ AccountIds ids;
+ bool is_signed_in;
+ };
+
+ void NotifyAccountAdded(const AccountState& account);
+ void NotifyAccountRemoved(const AccountState& account);
+ void NotifySignInChanged(const AccountState& account);
+
+ void UpdateSignInState(const std::string account_key, bool is_signed_in);
+
+ void StartTrackingAccount(const std::string account_key);
+ void StopTrackingAccount(const std::string account_key);
+ void StopTrackingAllAccounts();
+ void StartFetchingUserInfo(const std::string account_key);
+ void DeleteFetcher(AccountIdFetcher* fetcher);
+
+ IdentityProvider* identity_provider_; // Not owned.
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ std::map<std::string, AccountIdFetcher*> user_info_requests_;
+ std::map<std::string, AccountState> accounts_;
+ ObserverList<Observer> observer_list_;
+ bool shutdown_called_;
+};
+
+class AccountIdFetcher : public OAuth2TokenService::Consumer,
+ public gaia::GaiaOAuthClient::Delegate {
+ public:
+ AccountIdFetcher(OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context_getter,
+ AccountTracker* tracker,
+ const std::string& account_key);
+ virtual ~AccountIdFetcher();
+
+ const std::string& account_key() { return account_key_; }
+
+ void Start();
+
+ // OAuth2TokenService::Consumer implementation.
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // gaia::GaiaOAuthClient::Delegate implementation.
+ virtual void OnGetUserIdResponse(const std::string& gaia_id) OVERRIDE;
+ virtual void OnOAuthError() OVERRIDE;
+ virtual void OnNetworkError(int response_code) OVERRIDE;
+
+ private:
+ OAuth2TokenService* token_service_;
+ net::URLRequestContextGetter* request_context_getter_;
+ AccountTracker* tracker_;
+ const std::string account_key_;
+
+ scoped_ptr<OAuth2TokenService::Request> login_token_request_;
+ scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_;
+};
+
+} // namespace extensions
+
+#endif // GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_
diff --git a/chromium/google_apis/gaia/account_tracker_unittest.cc b/chromium/google_apis/gaia/account_tracker_unittest.cc
new file mode 100644
index 00000000000..fe8346eeb25
--- /dev/null
+++ b/chromium/google_apis/gaia/account_tracker_unittest.cc
@@ -0,0 +1,816 @@
+// Copyright 2014 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 "google_apis/gaia/account_tracker.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "google_apis/gaia/fake_identity_provider.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kPrimaryAccountKey[] = "primary_account@example.com";
+
+enum TrackingEventType {
+ ADDED,
+ REMOVED,
+ SIGN_IN,
+ SIGN_OUT
+};
+
+std::string AccountKeyToObfuscatedId(const std::string email) {
+ return "obfid-" + email;
+}
+
+class TrackingEvent {
+ public:
+ TrackingEvent(TrackingEventType type,
+ const std::string& account_key,
+ const std::string& gaia_id)
+ : type_(type),
+ account_key_(account_key),
+ gaia_id_(gaia_id) {}
+
+ TrackingEvent(TrackingEventType type,
+ const std::string& account_key)
+ : type_(type),
+ account_key_(account_key),
+ gaia_id_(AccountKeyToObfuscatedId(account_key)) {}
+
+ bool operator==(const TrackingEvent& event) const {
+ return type_ == event.type_ && account_key_ == event.account_key_ &&
+ gaia_id_ == event.gaia_id_;
+ }
+
+ std::string ToString() const {
+ const char * typestr = "INVALID";
+ switch (type_) {
+ case ADDED:
+ typestr = "ADD";
+ break;
+ case REMOVED:
+ typestr = "REM";
+ break;
+ case SIGN_IN:
+ typestr = " IN";
+ break;
+ case SIGN_OUT:
+ typestr = "OUT";
+ break;
+ }
+ return base::StringPrintf("{ type: %s, email: %s, gaia: %s }",
+ typestr,
+ account_key_.c_str(),
+ gaia_id_.c_str());
+ }
+
+ private:
+ friend bool CompareByUser(TrackingEvent a, TrackingEvent b);
+
+ TrackingEventType type_;
+ std::string account_key_;
+ std::string gaia_id_;
+};
+
+bool CompareByUser(TrackingEvent a, TrackingEvent b) {
+ return a.account_key_ < b.account_key_;
+}
+
+std::string Str(const std::vector<TrackingEvent>& events) {
+ std::string str = "[";
+ bool needs_comma = false;
+ for (std::vector<TrackingEvent>::const_iterator it =
+ events.begin(); it != events.end(); ++it) {
+ if (needs_comma)
+ str += ",\n ";
+ needs_comma = true;
+ str += it->ToString();
+ }
+ str += "]";
+ return str;
+}
+
+} // namespace
+
+namespace gaia {
+
+class AccountTrackerObserver : public AccountTracker::Observer {
+ public:
+ AccountTrackerObserver() {}
+ virtual ~AccountTrackerObserver() {}
+
+ testing::AssertionResult CheckEvents();
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1);
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1,
+ const TrackingEvent& e2);
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3);
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4);
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4,
+ const TrackingEvent& e5);
+ testing::AssertionResult CheckEvents(const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4,
+ const TrackingEvent& e5,
+ const TrackingEvent& e6);
+ void Clear();
+ void SortEventsByUser();
+
+ // AccountTracker::Observer implementation
+ virtual void OnAccountAdded(const AccountIds& ids) OVERRIDE;
+ virtual void OnAccountRemoved(const AccountIds& ids) OVERRIDE;
+ virtual void OnAccountSignInChanged(const AccountIds& ids, bool is_signed_in)
+ OVERRIDE;
+
+ private:
+ testing::AssertionResult CheckEvents(
+ const std::vector<TrackingEvent>& events);
+
+ std::vector<TrackingEvent> events_;
+};
+
+void AccountTrackerObserver::OnAccountAdded(const AccountIds& ids) {
+ events_.push_back(TrackingEvent(ADDED, ids.email, ids.gaia));
+}
+
+void AccountTrackerObserver::OnAccountRemoved(const AccountIds& ids) {
+ events_.push_back(TrackingEvent(REMOVED, ids.email, ids.gaia));
+}
+
+void AccountTrackerObserver::OnAccountSignInChanged(const AccountIds& ids,
+ bool is_signed_in) {
+ events_.push_back(
+ TrackingEvent(is_signed_in ? SIGN_IN : SIGN_OUT, ids.email, ids.gaia));
+}
+
+void AccountTrackerObserver::Clear() {
+ events_.clear();
+}
+
+void AccountTrackerObserver::SortEventsByUser() {
+ std::stable_sort(events_.begin(), events_.end(), CompareByUser);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents() {
+ std::vector<TrackingEvent> events;
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1,
+ const TrackingEvent& e2) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ events.push_back(e2);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ events.push_back(e2);
+ events.push_back(e3);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ events.push_back(e2);
+ events.push_back(e3);
+ events.push_back(e4);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4,
+ const TrackingEvent& e5) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ events.push_back(e2);
+ events.push_back(e3);
+ events.push_back(e4);
+ events.push_back(e5);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const TrackingEvent& e1,
+ const TrackingEvent& e2,
+ const TrackingEvent& e3,
+ const TrackingEvent& e4,
+ const TrackingEvent& e5,
+ const TrackingEvent& e6) {
+ std::vector<TrackingEvent> events;
+ events.push_back(e1);
+ events.push_back(e2);
+ events.push_back(e3);
+ events.push_back(e4);
+ events.push_back(e5);
+ events.push_back(e6);
+ return CheckEvents(events);
+}
+
+testing::AssertionResult AccountTrackerObserver::CheckEvents(
+ const std::vector<TrackingEvent>& events) {
+ std::string maybe_newline = (events.size() + events_.size()) > 2 ? "\n" : "";
+ testing::AssertionResult result(
+ (events_ == events)
+ ? testing::AssertionSuccess()
+ : (testing::AssertionFailure()
+ << "Expected " << maybe_newline << Str(events) << ", "
+ << maybe_newline << "Got " << maybe_newline << Str(events_)));
+ events_.clear();
+ return result;
+}
+
+class IdentityAccountTrackerTest : public testing::Test {
+ public:
+ IdentityAccountTrackerTest() {}
+
+ virtual ~IdentityAccountTrackerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+
+ fake_oauth2_token_service_.reset(new FakeOAuth2TokenService());
+
+ fake_identity_provider_.reset(
+ new FakeIdentityProvider(fake_oauth2_token_service_.get()));
+
+ account_tracker_.reset(
+ new AccountTracker(fake_identity_provider_.get(),
+ new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())));
+ account_tracker_->AddObserver(&observer_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ account_tracker_->RemoveObserver(&observer_);
+ account_tracker_->Shutdown();
+ }
+
+ AccountTrackerObserver* observer() {
+ return &observer_;
+ }
+
+ AccountTracker* account_tracker() {
+ return account_tracker_.get();
+ }
+
+ // Helpers to pass fake events to the tracker.
+
+ void NotifyLogin(const std::string account_key) {
+ identity_provider()->LogIn(account_key);
+ }
+
+ void NotifyLogout() { identity_provider()->LogOut(); }
+
+ void NotifyTokenAvailable(const std::string& username) {
+ fake_oauth2_token_service_->AddAccount(username);
+ }
+
+ void NotifyTokenRevoked(const std::string& username) {
+ fake_oauth2_token_service_->RemoveAccount(username);
+ }
+
+ // Helpers to fake access token and user info fetching
+ void IssueAccessToken(const std::string& username) {
+ fake_oauth2_token_service_->IssueAllTokensForAccount(
+ username, "access_token-" + username, base::Time::Max());
+ }
+
+ std::string GetValidTokenInfoResponse(const std::string account_key) {
+ return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) +
+ "\" }";
+ }
+
+ void ReturnOAuthUrlFetchResults(int fetcher_id,
+ net::HttpStatusCode response_code,
+ const std::string& response_string);
+
+ void ReturnOAuthUrlFetchSuccess(const std::string& account_key);
+ void ReturnOAuthUrlFetchFailure(const std::string& account_key);
+
+ void SetupPrimaryLogin() {
+ // Initial setup for tests that start with a signed in profile.
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+ }
+
+ std::string active_account_id() {
+ return identity_provider()->GetActiveAccountId();
+ }
+
+ private:
+ FakeIdentityProvider* identity_provider() {
+ return static_cast<FakeIdentityProvider*>(
+ account_tracker_->identity_provider());
+ }
+
+ base::MessageLoopForIO message_loop_; // net:: stuff needs IO message loop.
+ net::TestURLFetcherFactory test_fetcher_factory_;
+ scoped_ptr<FakeOAuth2TokenService> fake_oauth2_token_service_;
+ scoped_ptr<FakeIdentityProvider> fake_identity_provider_;
+
+ scoped_ptr<AccountTracker> account_tracker_;
+ AccountTrackerObserver observer_;
+};
+
+void IdentityAccountTrackerTest::ReturnOAuthUrlFetchResults(
+ int fetcher_id,
+ net::HttpStatusCode response_code,
+ const std::string& response_string) {
+
+ net::TestURLFetcher* fetcher =
+ test_fetcher_factory_.GetFetcherByID(fetcher_id);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(response_code);
+ fetcher->SetResponseString(response_string);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+void IdentityAccountTrackerTest::ReturnOAuthUrlFetchSuccess(
+ const std::string& account_key) {
+ IssueAccessToken(account_key);
+ ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse(account_key));
+}
+
+void IdentityAccountTrackerTest::ReturnOAuthUrlFetchFailure(
+ const std::string& account_key) {
+ IssueAccessToken(account_key);
+ ReturnOAuthUrlFetchResults(
+ gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, "");
+}
+
+// Primary tests just involve the Active account
+
+TEST_F(IdentityAccountTrackerTest, PrimaryNoEventsBeforeLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLoginThenTokenAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableThenLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableAndRevokedThenLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogout) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+
+ NotifyLogout();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(REMOVED, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogin) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyLogin(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenTokenAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLogoutThenRevoke) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->Clear();
+
+ NotifyLogout();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey)));
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, PrimaryLogoutFetchCancelAvailable) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ // TokenAvailable kicks off a fetch. Logout without satisfying it.
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+// Non-primary accounts
+
+TEST_F(IdentityAccountTrackerTest, Available) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, Revoke) {
+ SetupPrimaryLogin();
+
+ account_tracker()->OnRefreshTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com"),
+ TrackingEvent(SIGN_OUT, "user@example.com")));
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailableWithPendingFetch) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableRevokeRevoke) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com"),
+ TrackingEvent(SIGN_OUT, "user@example.com")));
+
+ NotifyTokenRevoked("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+
+ NotifyTokenAvailable("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, TwoAccounts) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "alpha@example.com"),
+ TrackingEvent(SIGN_IN, "alpha@example.com")));
+
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com")));
+
+ NotifyTokenRevoked("alpha@example.com");
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, "alpha@example.com")));
+
+ NotifyTokenRevoked("beta@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, "beta@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, AvailableTokenFetchFailAvailable) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchFailure("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "user@example.com"),
+ TrackingEvent(SIGN_IN, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiSignOutSignIn) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "alpha@example.com"),
+ TrackingEvent(SIGN_IN, "alpha@example.com"),
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com")));
+
+ NotifyLogout();
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, "alpha@example.com"),
+ TrackingEvent(REMOVED, "alpha@example.com"),
+ TrackingEvent(SIGN_OUT, "beta@example.com"),
+ TrackingEvent(REMOVED, "beta@example.com"),
+ TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey)));
+
+ // No events fire at all while profile is signed out.
+ NotifyTokenRevoked("alpha@example.com");
+ NotifyTokenAvailable("gamma@example.com");
+ EXPECT_TRUE(observer()->CheckEvents());
+
+ // Signing the profile in again will resume tracking all accounts.
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("gamma@example.com");
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(ADDED, "beta@example.com"),
+ TrackingEvent(SIGN_IN, "beta@example.com"),
+ TrackingEvent(ADDED, "gamma@example.com"),
+ TrackingEvent(SIGN_IN, "gamma@example.com"),
+ TrackingEvent(ADDED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+
+ // Revoking the primary token does not affect other accounts.
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ EXPECT_TRUE(observer()->CheckEvents(
+ TrackingEvent(SIGN_IN, kPrimaryAccountKey)));
+}
+
+// Primary/non-primary interactions
+
+TEST_F(IdentityAccountTrackerTest, MultiNoEventsBeforeLogin) {
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ NotifyTokenRevoked("user@example.com");
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ NotifyLogout();
+ EXPECT_TRUE(observer()->CheckEvents());
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiLogoutRemovesAllAccounts) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ observer()->Clear();
+
+ NotifyLogout();
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey),
+ TrackingEvent(REMOVED, kPrimaryAccountKey),
+ TrackingEvent(SIGN_OUT, "user@example.com"),
+ TrackingEvent(REMOVED, "user@example.com")));
+}
+
+TEST_F(IdentityAccountTrackerTest, MultiRevokePrimaryDoesNotRemoveAllAccounts) {
+ NotifyLogin(kPrimaryAccountKey);
+ NotifyTokenAvailable(kPrimaryAccountKey);
+ ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey);
+ NotifyTokenAvailable("user@example.com");
+ ReturnOAuthUrlFetchSuccess("user@example.com");
+ observer()->Clear();
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+ observer()->SortEventsByUser();
+ EXPECT_TRUE(
+ observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey)));
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsPrimary) {
+ SetupPrimaryLogin();
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(1ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsSignedOut) {
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(0ul, ids.size());
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsOnlyReturnAccountsWithTokens) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("alpha@example.com");
+ NotifyTokenAvailable("beta@example.com");
+ ReturnOAuthUrlFetchSuccess("beta@example.com");
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(2ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+ EXPECT_EQ("beta@example.com", ids[1].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("beta@example.com"), ids[1].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, GetAccountsSortOrder) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ // The primary account will be first in the vector. Remaining accounts
+ // will be sorted by gaia ID.
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(3ul, ids.size());
+ EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia);
+ EXPECT_EQ("alpha@example.com", ids[1].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("alpha@example.com"), ids[1].gaia);
+ EXPECT_EQ("zeta@example.com", ids[2].account_key);
+ EXPECT_EQ(AccountKeyToObfuscatedId("zeta@example.com"), ids[2].gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest,
+ GetAccountsReturnNothingWhenPrimarySignedOut) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+
+ std::vector<AccountIds> ids = account_tracker()->GetAccounts();
+ EXPECT_EQ(0ul, ids.size());
+}
+
+TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdPrimary) {
+ SetupPrimaryLogin();
+
+ AccountIds ids = account_tracker()->FindAccountIdsByGaiaId(
+ AccountKeyToObfuscatedId(kPrimaryAccountKey));
+ EXPECT_EQ(kPrimaryAccountKey, ids.account_key);
+ EXPECT_EQ(kPrimaryAccountKey, ids.email);
+ EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids.gaia);
+}
+
+TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdNotFound) {
+ SetupPrimaryLogin();
+
+ AccountIds ids = account_tracker()->FindAccountIdsByGaiaId(
+ AccountKeyToObfuscatedId("notfound@example.com"));
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+}
+
+TEST_F(IdentityAccountTrackerTest,
+ FindAccountIdsByGaiaIdReturnEmptyWhenPrimarySignedOut) {
+ SetupPrimaryLogin();
+
+ NotifyTokenAvailable("zeta@example.com");
+ ReturnOAuthUrlFetchSuccess("zeta@example.com");
+ NotifyTokenAvailable("alpha@example.com");
+ ReturnOAuthUrlFetchSuccess("alpha@example.com");
+
+ NotifyTokenRevoked(kPrimaryAccountKey);
+
+ AccountIds ids =
+ account_tracker()->FindAccountIdsByGaiaId(kPrimaryAccountKey);
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+
+ ids = account_tracker()->FindAccountIdsByGaiaId("alpha@example.com");
+ EXPECT_TRUE(ids.account_key.empty());
+ EXPECT_TRUE(ids.email.empty());
+ EXPECT_TRUE(ids.gaia.empty());
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/fake_gaia.cc b/chromium/google_apis/gaia/fake_gaia.cc
index 322ed54ff9f..4cfe6040e53 100644
--- a/chromium/google_apis/gaia/fake_gaia.cc
+++ b/chromium/google_apis/gaia/fake_gaia.cc
@@ -4,35 +4,123 @@
#include "google_apis/gaia/fake_gaia.h"
+#include <vector>
+
#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/values.h"
+#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/base/url_util.h"
+#include "net/cookies/parsed_cookie.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "url/url_parse.h"
+#define REGISTER_RESPONSE_HANDLER(url, method) \
+ request_handlers_.insert(std::make_pair( \
+ url.path(), base::Bind(&FakeGaia::method, base::Unretained(this))))
+
+#define REGISTER_PATH_RESPONSE_HANDLER(path, method) \
+ request_handlers_.insert(std::make_pair( \
+ path, base::Bind(&FakeGaia::method, base::Unretained(this))))
+
using namespace net::test_server;
namespace {
+
const base::FilePath::CharType kServiceLogin[] =
FILE_PATH_LITERAL("google_apis/test/service_login.html");
+
+// OAuth2 Authentication header value prefix.
+const char kAuthHeaderBearer[] = "Bearer ";
+const char kAuthHeaderOAuth[] = "OAuth ";
+
+const char kListAccountsResponseFormat[] =
+ "[\"gaia.l.a.r\",[[\"gaia.l.a\",1,\"\",\"%s\",\"\",1,1,0]]]";
+
+typedef std::map<std::string, std::string> CookieMap;
+
+// Parses cookie name-value map our of |request|.
+CookieMap GetRequestCookies(const HttpRequest& request) {
+ CookieMap result;
+ std::map<std::string, std::string>::const_iterator iter =
+ request.headers.find("Cookie");
+ if (iter != request.headers.end()) {
+ std::vector<std::string> cookie_nv_pairs;
+ base::SplitString(iter->second, ' ', &cookie_nv_pairs);
+ for(std::vector<std::string>::const_iterator cookie_line =
+ cookie_nv_pairs.begin();
+ cookie_line != cookie_nv_pairs.end();
+ ++cookie_line) {
+ std::vector<std::string> name_value;
+ base::SplitString(*cookie_line, '=', &name_value);
+ if (name_value.size() != 2)
+ continue;
+
+ std::string value = name_value[1];
+ if (value.size() && value[value.size() - 1] == ';')
+ value = value.substr(0, value.size() -1);
+
+ result.insert(std::make_pair(name_value[0], value));
+ }
+ }
+ return result;
+}
+
+// Extracts the |access_token| from authorization header of |request|.
+bool GetAccessToken(const HttpRequest& request,
+ const char* auth_token_prefix,
+ std::string* access_token) {
+ std::map<std::string, std::string>::const_iterator auth_header_entry =
+ request.headers.find("Authorization");
+ if (auth_header_entry != request.headers.end()) {
+ if (StartsWithASCII(auth_header_entry->second, auth_token_prefix, true)) {
+ *access_token = auth_header_entry->second.substr(
+ strlen(auth_token_prefix));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SetCookies(BasicHttpResponse* http_response,
+ const std::string& sid_cookie,
+ const std::string& lsid_cookie) {
+ http_response->AddCustomHeader(
+ "Set-Cookie",
+ base::StringPrintf("SID=%s; Path=/; HttpOnly;", sid_cookie.c_str()));
+ http_response->AddCustomHeader(
+ "Set-Cookie",
+ base::StringPrintf("LSID=%s; Path=/; HttpOnly;", lsid_cookie.c_str()));
}
+} // namespace
+
FakeGaia::AccessTokenInfo::AccessTokenInfo()
: expires_in(3600) {}
FakeGaia::AccessTokenInfo::~AccessTokenInfo() {}
+FakeGaia::MergeSessionParams::MergeSessionParams() {
+}
+
+FakeGaia::MergeSessionParams::~MergeSessionParams() {
+}
+
FakeGaia::FakeGaia() {
base::FilePath source_root_dir;
PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
@@ -43,100 +131,66 @@ FakeGaia::FakeGaia() {
FakeGaia::~FakeGaia() {}
-scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) {
+void FakeGaia::SetMergeSessionParams(
+ const MergeSessionParams& params) {
+ merge_session_params_ = params;
+}
+
+void FakeGaia::Initialize() {
GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
+ // Handles /MergeSession GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->merge_session_url(), HandleMergeSession);
+
+ // Handles /o/oauth2/programmatic_auth GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->client_login_to_oauth2_url(), HandleProgramaticAuth);
+
+ // Handles /ServiceLogin GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->service_login_url(), HandleServiceLogin);
+ // Handles /OAuthLogin GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->oauth1_login_url(), HandleOAuthLogin);
+
+ // Handles /ServiceLoginAuth GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->service_login_auth_url(), HandleServiceLoginAuth);
+
+ // Handles /SSO GAIA call (not GAIA, made up for SAML tests).
+ REGISTER_PATH_RESPONSE_HANDLER("/SSO", HandleSSO);
+
+ // Handles /o/oauth2/token GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->oauth2_token_url(), HandleAuthToken);
+
+ // Handles /oauth2/v2/tokeninfo GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->oauth2_token_info_url(), HandleTokenInfo);
+
+ // Handles /oauth2/v2/IssueToken GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->oauth2_issue_token_url(), HandleIssueToken);
+
+ // Handles /ListAccounts GAIA call.
+ REGISTER_RESPONSE_HANDLER(
+ gaia_urls->list_accounts_url(), HandleListAccounts);
+}
+
+scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) {
// The scheme and host of the URL is actually not important but required to
// get a valid GURL in order to parse |request.relative_url|.
GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
std::string request_path = request_url.path();
-
scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
- if (request_path == gaia_urls->service_login_url().path()) {
- http_response->set_code(net::HTTP_OK);
- http_response->set_content(service_login_response_);
- http_response->set_content_type("text/html");
- } else if (request_path == gaia_urls->service_login_auth_url().path()) {
- std::string continue_url = gaia_urls->service_login_url().spec();
- GetQueryParameter(request.content, "continue", &continue_url);
- http_response->set_code(net::HTTP_OK);
- const std::string redirect_js =
- "document.location.href = '" + continue_url + "'";
- http_response->set_content(
- "<HTML><HEAD><SCRIPT>\n" + redirect_js + "\n</SCRIPT></HEAD></HTML>");
- http_response->set_content_type("text/html");
- } else if (request_path == gaia_urls->oauth2_token_url().path()) {
- std::string refresh_token;
- std::string client_id;
- std::string scope;
- const AccessTokenInfo* token_info = NULL;
- GetQueryParameter(request.content, "scope", &scope);
- if (GetQueryParameter(request.content, "refresh_token", &refresh_token) &&
- GetQueryParameter(request.content, "client_id", &client_id) &&
- (token_info = GetAccessTokenInfo(refresh_token, client_id, scope))) {
- base::DictionaryValue response_dict;
- response_dict.SetString("access_token", token_info->token);
- response_dict.SetInteger("expires_in", 3600);
- FormatJSONResponse(response_dict, http_response.get());
- } else {
- http_response->set_code(net::HTTP_BAD_REQUEST);
- }
- } else if (request_path == gaia_urls->oauth2_token_info_url().path()) {
- const AccessTokenInfo* token_info = NULL;
- std::string access_token;
- if (GetQueryParameter(request.content, "access_token", &access_token)) {
- for (AccessTokenInfoMap::const_iterator entry(
- access_token_info_map_.begin());
- entry != access_token_info_map_.end();
- ++entry) {
- if (entry->second.token == access_token) {
- token_info = &(entry->second);
- break;
- }
- }
- }
-
- if (token_info) {
- base::DictionaryValue response_dict;
- response_dict.SetString("issued_to", token_info->issued_to);
- response_dict.SetString("audience", token_info->audience);
- response_dict.SetString("user_id", token_info->user_id);
- std::vector<std::string> scope_vector(token_info->scopes.begin(),
- token_info->scopes.end());
- response_dict.SetString("scope", JoinString(scope_vector, " "));
- response_dict.SetInteger("expires_in", token_info->expires_in);
- response_dict.SetString("email", token_info->email);
- FormatJSONResponse(response_dict, http_response.get());
- } else {
- http_response->set_code(net::HTTP_BAD_REQUEST);
- }
- } else if (request_path == gaia_urls->oauth2_issue_token_url().path()) {
- std::string access_token;
- std::map<std::string, std::string>::const_iterator auth_header_entry =
- request.headers.find("Authorization");
- if (auth_header_entry != request.headers.end()) {
- if (StartsWithASCII(auth_header_entry->second, "Bearer ", true))
- access_token = auth_header_entry->second.substr(7);
- }
-
- std::string scope;
- std::string client_id;
- const AccessTokenInfo* token_info = NULL;
- if (GetQueryParameter(request.content, "scope", &scope) &&
- GetQueryParameter(request.content, "client_id", &client_id) &&
- (token_info = GetAccessTokenInfo(access_token, client_id, scope))) {
- base::DictionaryValue response_dict;
- response_dict.SetString("issueAdvice", "auto");
- response_dict.SetString("expiresIn",
- base::IntToString(token_info->expires_in));
- response_dict.SetString("token", token_info->token);
- FormatJSONResponse(response_dict, http_response.get());
- } else {
- http_response->set_code(net::HTTP_BAD_REQUEST);
- }
+ RequestHandlerMap::iterator iter = request_handlers_.find(request_path);
+ if (iter != request_handlers_.end()) {
+ LOG(WARNING) << "Serving request " << request_path;
+ iter->second.Run(request, http_response.get());
} else {
- // Request not understood.
- return scoped_ptr<HttpResponse>();
+ LOG(ERROR) << "Unhandled request " << request_path;
+ return scoped_ptr<HttpResponse>(); // Request not understood.
}
return http_response.PassAs<HttpResponse>();
@@ -147,6 +201,102 @@ void FakeGaia::IssueOAuthToken(const std::string& auth_token,
access_token_info_map_.insert(std::make_pair(auth_token, token_info));
}
+void FakeGaia::RegisterSamlUser(const std::string& account_id,
+ const GURL& saml_idp) {
+ saml_account_idp_map_[account_id] = saml_idp;
+}
+
+// static
+bool FakeGaia::GetQueryParameter(const std::string& query,
+ const std::string& key,
+ std::string* value) {
+ // Name and scheme actually don't matter, but are required to get a valid URL
+ // for parsing.
+ GURL query_url("http://localhost?" + query);
+ return net::GetValueForKeyInQuery(query_url, key, value);
+}
+
+void FakeGaia::HandleMergeSession(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ http_response->set_code(net::HTTP_UNAUTHORIZED);
+ if (merge_session_params_.session_sid_cookie.empty() ||
+ merge_session_params_.session_lsid_cookie.empty()) {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ return;
+ }
+
+ std::string uber_token;
+ if (!GetQueryParameter(request.content, "uberauth", &uber_token) ||
+ uber_token != merge_session_params_.gaia_uber_token) {
+ LOG(ERROR) << "Missing or invalid 'uberauth' param in /MergeSession call";
+ return;
+ }
+
+ std::string continue_url;
+ if (!GetQueryParameter(request.content, "continue", &continue_url)) {
+ LOG(ERROR) << "Missing or invalid 'continue' param in /MergeSession call";
+ return;
+ }
+
+ std::string source;
+ if (!GetQueryParameter(request.content, "source", &source)) {
+ LOG(ERROR) << "Missing or invalid 'source' param in /MergeSession call";
+ return;
+ }
+
+ SetCookies(http_response,
+ merge_session_params_.session_sid_cookie,
+ merge_session_params_.session_lsid_cookie);
+ // TODO(zelidrag): Not used now.
+ http_response->set_content("OK");
+ http_response->set_code(net::HTTP_OK);
+}
+
+void FakeGaia::HandleProgramaticAuth(
+ const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ http_response->set_code(net::HTTP_UNAUTHORIZED);
+ if (merge_session_params_.auth_code.empty()) {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ return;
+ }
+
+ GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
+ std::string scope;
+ if (!GetQueryParameter(request.content, "scope", &scope) ||
+ GaiaConstants::kOAuth1LoginScope != scope) {
+ return;
+ }
+
+ CookieMap cookies = GetRequestCookies(request);
+ CookieMap::const_iterator sid_iter = cookies.find("SID");
+ if (sid_iter == cookies.end() ||
+ sid_iter->second != merge_session_params_.auth_sid_cookie) {
+ LOG(ERROR) << "/o/oauth2/programmatic_auth missing SID cookie";
+ return;
+ }
+ CookieMap::const_iterator lsid_iter = cookies.find("LSID");
+ if (lsid_iter == cookies.end() ||
+ lsid_iter->second != merge_session_params_.auth_lsid_cookie) {
+ LOG(ERROR) << "/o/oauth2/programmatic_auth missing LSID cookie";
+ return;
+ }
+
+ std::string client_id;
+ if (!GetQueryParameter(request.content, "client_id", &client_id) ||
+ gaia_urls->oauth2_chrome_client_id() != client_id) {
+ return;
+ }
+
+ http_response->AddCustomHeader(
+ "Set-Cookie",
+ base::StringPrintf(
+ "oauth_code=%s; Path=/o/GetOAuth2Token; Secure; HttpOnly;",
+ merge_session_params_.auth_code.c_str()));
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content_type("text/html");
+}
+
void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict,
BasicHttpResponse* http_response) {
std::string response_json;
@@ -155,7 +305,7 @@ void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict,
http_response->set_code(net::HTTP_OK);
}
-const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo(
+const FakeGaia::AccessTokenInfo* FakeGaia::FindAccessTokenInfo(
const std::string& auth_token,
const std::string& client_id,
const std::string& scope_string) const {
@@ -179,12 +329,203 @@ const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo(
return NULL;
}
-// static
-bool FakeGaia::GetQueryParameter(const std::string& query,
- const std::string& key,
- std::string* value) {
- // Name and scheme actually don't matter, but are required to get a valid URL
- // for parsing.
- GURL query_url("http://localhost?" + query);
- return net::GetValueForKeyInQuery(query_url, key, value);
+void FakeGaia::HandleServiceLogin(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(service_login_response_);
+ http_response->set_content_type("text/html");
+}
+
+void FakeGaia::HandleOAuthLogin(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ http_response->set_code(net::HTTP_UNAUTHORIZED);
+ if (merge_session_params_.gaia_uber_token.empty()) {
+ http_response->set_code(net::HTTP_FORBIDDEN);
+ return;
+ }
+
+ std::string access_token;
+ if (!GetAccessToken(request, kAuthHeaderOAuth, &access_token)) {
+ LOG(ERROR) << "/OAuthLogin missing access token in the header";
+ return;
+ }
+
+ GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
+ std::string request_query = request_url.query();
+
+ std::string source;
+ if (!GetQueryParameter(request_query, "source", &source)) {
+ LOG(ERROR) << "Missing 'source' param in /OAuthLogin call";
+ return;
+ }
+
+ std::string issue_uberauth;
+ if (GetQueryParameter(request_query, "issueuberauth", &issue_uberauth) &&
+ issue_uberauth == "1") {
+ http_response->set_content(merge_session_params_.gaia_uber_token);
+ http_response->set_code(net::HTTP_OK);
+ // Issue GAIA uber token.
+ } else {
+ LOG(FATAL) << "/OAuthLogin for SID/LSID is not supported";
+ }
+}
+
+void FakeGaia::HandleServiceLoginAuth(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ std::string continue_url =
+ GaiaUrls::GetInstance()->service_login_url().spec();
+ GetQueryParameter(request.content, "continue", &continue_url);
+
+ std::string redirect_url = continue_url;
+
+ std::string email;
+ if (GetQueryParameter(request.content, "Email", &email) &&
+ saml_account_idp_map_.find(email) != saml_account_idp_map_.end()) {
+ GURL url(saml_account_idp_map_[email]);
+ url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request");
+ url = net::AppendQueryParameter(url, "RelayState", continue_url);
+ redirect_url = url.spec();
+ } else if (!merge_session_params_.auth_sid_cookie.empty() &&
+ !merge_session_params_.auth_lsid_cookie.empty()) {
+ SetCookies(http_response,
+ merge_session_params_.auth_sid_cookie,
+ merge_session_params_.auth_lsid_cookie);
+ }
+
+ http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
+ http_response->AddCustomHeader("Location", redirect_url);
+}
+
+void FakeGaia::HandleSSO(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ if (!merge_session_params_.auth_sid_cookie.empty() &&
+ !merge_session_params_.auth_lsid_cookie.empty()) {
+ SetCookies(http_response,
+ merge_session_params_.auth_sid_cookie,
+ merge_session_params_.auth_lsid_cookie);
+ }
+ std::string relay_state;
+ GetQueryParameter(request.content, "RelayState", &relay_state);
+ std::string redirect_url = relay_state;
+ http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
+ http_response->AddCustomHeader("Location", redirect_url);
+}
+
+void FakeGaia::HandleAuthToken(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ std::string grant_type;
+ std::string refresh_token;
+ std::string client_id;
+ std::string scope;
+ std::string auth_code;
+ const AccessTokenInfo* token_info = NULL;
+ GetQueryParameter(request.content, "scope", &scope);
+
+ if (!GetQueryParameter(request.content, "grant_type", &grant_type)) {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ LOG(ERROR) << "No 'grant_type' param in /o/oauth2/token";
+ return;
+ }
+
+ if (grant_type == "authorization_code") {
+ if (!GetQueryParameter(request.content, "code", &auth_code) ||
+ auth_code != merge_session_params_.auth_code) {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ LOG(ERROR) << "No 'code' param in /o/oauth2/token";
+ return;
+ }
+
+ if (GaiaConstants::kOAuth1LoginScope != scope) {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ LOG(ERROR) << "Invalid scope for /o/oauth2/token - " << scope;
+ return;
+ }
+
+ base::DictionaryValue response_dict;
+ response_dict.SetString("refresh_token",
+ merge_session_params_.refresh_token);
+ response_dict.SetString("access_token",
+ merge_session_params_.access_token);
+ response_dict.SetInteger("expires_in", 3600);
+ FormatJSONResponse(response_dict, http_response);
+ } else if (GetQueryParameter(request.content,
+ "refresh_token",
+ &refresh_token) &&
+ GetQueryParameter(request.content,
+ "client_id",
+ &client_id) &&
+ (token_info = FindAccessTokenInfo(refresh_token,
+ client_id,
+ scope))) {
+ base::DictionaryValue response_dict;
+ response_dict.SetString("access_token", token_info->token);
+ response_dict.SetInteger("expires_in", 3600);
+ FormatJSONResponse(response_dict, http_response);
+ } else {
+ LOG(ERROR) << "Bad request for /o/oauth2/token - "
+ << "refresh_token = " << refresh_token
+ << ", scope = " << scope
+ << ", client_id = " << client_id;
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ }
+}
+
+void FakeGaia::HandleTokenInfo(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ const AccessTokenInfo* token_info = NULL;
+ std::string access_token;
+ if (GetQueryParameter(request.content, "access_token", &access_token)) {
+ for (AccessTokenInfoMap::const_iterator entry(
+ access_token_info_map_.begin());
+ entry != access_token_info_map_.end();
+ ++entry) {
+ if (entry->second.token == access_token) {
+ token_info = &(entry->second);
+ break;
+ }
+ }
+ }
+
+ if (token_info) {
+ base::DictionaryValue response_dict;
+ response_dict.SetString("issued_to", token_info->issued_to);
+ response_dict.SetString("audience", token_info->audience);
+ response_dict.SetString("user_id", token_info->user_id);
+ std::vector<std::string> scope_vector(token_info->scopes.begin(),
+ token_info->scopes.end());
+ response_dict.SetString("scope", JoinString(scope_vector, " "));
+ response_dict.SetInteger("expires_in", token_info->expires_in);
+ response_dict.SetString("email", token_info->email);
+ FormatJSONResponse(response_dict, http_response);
+ } else {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ }
+}
+
+void FakeGaia::HandleIssueToken(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ std::string access_token;
+ std::string scope;
+ std::string client_id;
+ const AccessTokenInfo* token_info = NULL;
+ if (GetAccessToken(request, kAuthHeaderBearer, &access_token) &&
+ GetQueryParameter(request.content, "scope", &scope) &&
+ GetQueryParameter(request.content, "client_id", &client_id) &&
+ (token_info = FindAccessTokenInfo(access_token, client_id, scope))) {
+ base::DictionaryValue response_dict;
+ response_dict.SetString("issueAdvice", "auto");
+ response_dict.SetString("expiresIn",
+ base::IntToString(token_info->expires_in));
+ response_dict.SetString("token", token_info->token);
+ FormatJSONResponse(response_dict, http_response);
+ } else {
+ http_response->set_code(net::HTTP_BAD_REQUEST);
+ }
+}
+
+void FakeGaia::HandleListAccounts(const HttpRequest& request,
+ BasicHttpResponse* http_response) {
+ http_response->set_content(base::StringPrintf(
+ kListAccountsResponseFormat, merge_session_params_.email.c_str()));
+ http_response->set_code(net::HTTP_OK);
}
diff --git a/chromium/google_apis/gaia/fake_gaia.h b/chromium/google_apis/gaia/fake_gaia.h
index de4201f7337..13a63e6b228 100644
--- a/chromium/google_apis/gaia/fake_gaia.h
+++ b/chromium/google_apis/gaia/fake_gaia.h
@@ -10,7 +10,9 @@
#include <string>
#include "base/basictypes.h"
+#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
+#include "url/gurl.h"
namespace base {
class DictionaryValue;
@@ -45,8 +47,44 @@ class FakeGaia {
std::string email;
};
+ // Cookies and tokens for /MergeSession call seqeunce.
+ struct MergeSessionParams {
+ MergeSessionParams();
+ ~MergeSessionParams();
+
+ // Values of SID and LSID cookie that are set by /ServiceLoginAuth or its
+ // equivalent at the end of the SAML login flow.
+ std::string auth_sid_cookie;
+ std::string auth_lsid_cookie;
+
+ // auth_code cookie value response for /o/oauth2/programmatic_auth call.
+ std::string auth_code;
+
+ // OAuth2 refresh and access token generated by /o/oauth2/token call
+ // with "...&grant_type=authorization_code".
+ std::string refresh_token;
+ std::string access_token;
+
+ // Uber token response from /OAuthLogin call.
+ std::string gaia_uber_token;
+
+ // Values of SID and LSID cookie generated from /MergeSession call.
+ std::string session_sid_cookie;
+ std::string session_lsid_cookie;
+
+ // The e-mail address returned by /ListAccounts.
+ std::string email;
+ };
+
FakeGaia();
- ~FakeGaia();
+ virtual ~FakeGaia();
+
+ // Sets the initial value of tokens and cookies.
+ void SetMergeSessionParams(const MergeSessionParams& params);
+
+ // Initializes HTTP request handlers. Should be called after switches
+ // for tweaking GaiaUrls are in place.
+ void Initialize();
// Handles a request and returns a response if the request was recognized as a
// GAIA request. Note that this respects the switches::kGaiaUrl and friends so
@@ -61,30 +99,74 @@ class FakeGaia {
void IssueOAuthToken(const std::string& auth_token,
const AccessTokenInfo& token_info);
+ // Associates an account id with a SAML IdP redirect endpoint. When a
+ // /ServiceLoginAuth request comes in for that user, it will be redirected
+ // to the associated redirect endpoint.
+ void RegisterSamlUser(const std::string& account_id, const GURL& saml_idp);
+
+ // Extracts the parameter named |key| from |query| and places it in |value|.
+ // Returns false if no parameter is found.
+ static bool GetQueryParameter(const std::string& query,
+ const std::string& key,
+ std::string* value);
+ protected:
+ // HTTP handler for /MergeSession.
+ virtual void HandleMergeSession(
+ const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+
private:
typedef std::multimap<std::string, AccessTokenInfo> AccessTokenInfoMap;
+ typedef std::map<std::string, GURL> SamlAccountIdpMap;
// Formats a JSON response with the data in |response_dict|.
void FormatJSONResponse(const base::DictionaryValue& response_dict,
net::test_server::BasicHttpResponse* http_response);
+ typedef base::Callback<void(
+ const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response)>
+ HttpRequestHandlerCallback;
+ typedef std::map<std::string, HttpRequestHandlerCallback> RequestHandlerMap;
+
+ // HTTP request handlers.
+ void HandleProgramaticAuth(
+ const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleServiceLogin(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleOAuthLogin(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleServiceLoginAuth(
+ const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleSSO(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleAuthToken(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleTokenInfo(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleIssueToken(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandleListAccounts(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+ void HandlePeopleGet(const net::test_server::HttpRequest& request,
+ net::test_server::BasicHttpResponse* http_response);
+
// Returns the access token associated with |auth_token| that matches the
// given |client_id| and |scope_string|. If |scope_string| is empty, the first
// token satisfying the other criteria is returned. Returns NULL if no token
// matches.
- const AccessTokenInfo* GetAccessTokenInfo(const std::string& auth_token,
- const std::string& client_id,
- const std::string& scope_string)
+ const AccessTokenInfo* FindAccessTokenInfo(const std::string& auth_token,
+ const std::string& client_id,
+ const std::string& scope_string)
const;
- // Extracts the parameter named |key| from |query| and places it in |value|.
- // Returns false if no parameter is found.
- static bool GetQueryParameter(const std::string& query,
- const std::string& key,
- std::string* value);
-
+ MergeSessionParams merge_session_params_;
AccessTokenInfoMap access_token_info_map_;
+ RequestHandlerMap request_handlers_;
std::string service_login_response_;
+ SamlAccountIdpMap saml_account_idp_map_;
DISALLOW_COPY_AND_ASSIGN(FakeGaia);
};
diff --git a/chromium/google_apis/gaia/fake_identity_provider.cc b/chromium/google_apis/gaia/fake_identity_provider.cc
new file mode 100644
index 00000000000..7360e41bc1d
--- /dev/null
+++ b/chromium/google_apis/gaia/fake_identity_provider.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 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 "google_apis/gaia/fake_identity_provider.h"
+
+#include "google_apis/gaia/oauth2_token_service.h"
+
+FakeIdentityProvider::FakeIdentityProvider(OAuth2TokenService* token_service)
+ : token_service_(token_service) {
+}
+
+FakeIdentityProvider::~FakeIdentityProvider() {
+}
+
+void FakeIdentityProvider::LogIn(const std::string& account_id) {
+ account_id_ = account_id;
+ FireOnActiveAccountLogin();
+}
+
+void FakeIdentityProvider::LogOut() {
+ account_id_.clear();
+ FireOnActiveAccountLogout();
+}
+
+std::string FakeIdentityProvider::GetActiveUsername() {
+ return account_id_;
+}
+
+std::string FakeIdentityProvider::GetActiveAccountId() {
+ return account_id_;
+}
+
+OAuth2TokenService* FakeIdentityProvider::GetTokenService() {
+ return token_service_;
+}
+
+bool FakeIdentityProvider::RequestLogin() {
+ return false;
+}
diff --git a/chromium/google_apis/gaia/fake_identity_provider.h b/chromium/google_apis/gaia/fake_identity_provider.h
new file mode 100644
index 00000000000..28d46d940a5
--- /dev/null
+++ b/chromium/google_apis/gaia/fake_identity_provider.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_FAKE_IDENTITY_PROVIDER_H_
+#define GOOGLE_APIS_GAIA_FAKE_IDENTITY_PROVIDER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "google_apis/gaia/identity_provider.h"
+
+class OAuth2TokenService;
+
+// Fake identity provider implementation.
+class FakeIdentityProvider : public IdentityProvider {
+ public:
+ explicit FakeIdentityProvider(OAuth2TokenService* token_service);
+ virtual ~FakeIdentityProvider();
+
+ void LogIn(const std::string& account_id);
+ void LogOut();
+
+ // IdentityProvider:
+ virtual std::string GetActiveUsername() OVERRIDE;
+ virtual std::string GetActiveAccountId() OVERRIDE;
+ virtual OAuth2TokenService* GetTokenService() OVERRIDE;
+ virtual bool RequestLogin() OVERRIDE;
+
+ private:
+ std::string account_id_;
+ OAuth2TokenService* token_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeIdentityProvider);
+};
+
+#endif // GOOGLE_APIS_GAIA_FAKE_IDENTITY_PROVIDER_H_
diff --git a/chromium/google_apis/gaia/fake_oauth2_token_service.cc b/chromium/google_apis/gaia/fake_oauth2_token_service.cc
new file mode 100644
index 00000000000..0b01404bd87
--- /dev/null
+++ b/chromium/google_apis/gaia/fake_oauth2_token_service.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 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 "google_apis/gaia/fake_oauth2_token_service.h"
+
+FakeOAuth2TokenService::PendingRequest::PendingRequest() {
+}
+
+FakeOAuth2TokenService::PendingRequest::~PendingRequest() {
+}
+
+FakeOAuth2TokenService::FakeOAuth2TokenService() : request_context_(NULL) {
+}
+
+FakeOAuth2TokenService::~FakeOAuth2TokenService() {
+}
+
+std::vector<std::string> FakeOAuth2TokenService::GetAccounts() {
+ return std::vector<std::string>(account_ids_.begin(), account_ids_.end());
+}
+
+void FakeOAuth2TokenService::FetchOAuth2Token(
+ RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) {
+ PendingRequest pending_request;
+ pending_request.account_id = account_id;
+ pending_request.client_id = client_id;
+ pending_request.client_secret = client_secret;
+ pending_request.scopes = scopes;
+ pending_request.request = request->AsWeakPtr();
+ pending_requests_.push_back(pending_request);
+}
+
+void FakeOAuth2TokenService::InvalidateOAuth2Token(
+ const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) {
+}
+
+net::URLRequestContextGetter* FakeOAuth2TokenService::GetRequestContext() {
+ return request_context_;
+}
+
+bool FakeOAuth2TokenService::RefreshTokenIsAvailable(
+ const std::string& account_id) const {
+ return account_ids_.count(account_id) != 0;
+};
+
+void FakeOAuth2TokenService::AddAccount(const std::string& account_id) {
+ account_ids_.insert(account_id);
+ FireRefreshTokenAvailable(account_id);
+}
+
+void FakeOAuth2TokenService::RemoveAccount(const std::string& account_id) {
+ account_ids_.erase(account_id);
+ FireRefreshTokenRevoked(account_id);
+}
+
+void FakeOAuth2TokenService::IssueAllTokensForAccount(
+ const std::string& account_id,
+ const std::string& access_token,
+ const base::Time& expiration) {
+
+ // Walk the requests and notify the callbacks.
+ for (std::vector<PendingRequest>::iterator it = pending_requests_.begin();
+ it != pending_requests_.end(); ++it) {
+ if (it->request && (account_id == it->account_id)) {
+ it->request->InformConsumer(
+ GoogleServiceAuthError::AuthErrorNone(), access_token, expiration);
+ }
+ }
+}
+
+
+OAuth2AccessTokenFetcher* FakeOAuth2TokenService::CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) {
+ // |FakeOAuth2TokenService| overrides |FetchOAuth2Token| and thus
+ // |CreateAccessTokenFetcher| should never be called.
+ NOTREACHED();
+ return NULL;
+}
diff --git a/chromium/google_apis/gaia/fake_oauth2_token_service.h b/chromium/google_apis/gaia/fake_oauth2_token_service.h
new file mode 100644
index 00000000000..3bdb67500a9
--- /dev/null
+++ b/chromium/google_apis/gaia/fake_oauth2_token_service.h
@@ -0,0 +1,84 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_FAKE_OAUTH2_TOKEN_SERVICE_H_
+#define GOOGLE_APIS_GAIA_FAKE_OAUTH2_TOKEN_SERVICE_H_
+
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Do-nothing implementation of OAuth2TokenService.
+class FakeOAuth2TokenService : public OAuth2TokenService {
+ public:
+ FakeOAuth2TokenService();
+ virtual ~FakeOAuth2TokenService();
+
+ virtual std::vector<std::string> GetAccounts() OVERRIDE;
+
+ void AddAccount(const std::string& account_id);
+ void RemoveAccount(const std::string& account_id);
+
+ // Helper routines to issue tokens for pending requests.
+ void IssueAllTokensForAccount(const std::string& account_id,
+ const std::string& access_token,
+ const base::Time& expiration);
+
+ void set_request_context(net::URLRequestContextGetter* request_context) {
+ request_context_ = request_context;
+ }
+
+ protected:
+ // OAuth2TokenService overrides.
+ virtual void FetchOAuth2Token(RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) OVERRIDE;
+
+ virtual void InvalidateOAuth2Token(const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) OVERRIDE;
+
+ virtual bool RefreshTokenIsAvailable(const std::string& account_id) const
+ OVERRIDE;
+
+ private:
+ struct PendingRequest {
+ PendingRequest();
+ ~PendingRequest();
+
+ std::string account_id;
+ std::string client_id;
+ std::string client_secret;
+ ScopeSet scopes;
+ base::WeakPtr<RequestImpl> request;
+ };
+
+ // OAuth2TokenService overrides.
+ virtual net::URLRequestContextGetter* GetRequestContext() OVERRIDE;
+
+ virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) OVERRIDE;
+
+ std::set<std::string> account_ids_;
+ std::vector<PendingRequest> pending_requests_;
+
+ net::URLRequestContextGetter* request_context_; // weak
+
+ DISALLOW_COPY_AND_ASSIGN(FakeOAuth2TokenService);
+};
+
+#endif // GOOGLE_APIS_GAIA_FAKE_OAUTH2_TOKEN_SERVICE_H_
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher.cc b/chromium/google_apis/gaia/gaia_auth_fetcher.cc
index c5254f4235e..ca4cdc0588a 100644
--- a/chromium/google_apis/gaia/gaia_auth_fetcher.cc
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher.cc
@@ -4,7 +4,6 @@
#include "google_apis/gaia/gaia_auth_fetcher.h"
-#include <algorithm>
#include <string>
#include <utility>
#include <vector>
@@ -33,7 +32,12 @@ const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES |
static bool CookiePartsContains(const std::vector<std::string>& parts,
const char* part) {
- return std::find(parts.begin(), parts.end(), part) != parts.end();
+ for (std::vector<std::string>::const_iterator it = parts.begin();
+ it != parts.end(); ++it) {
+ if (LowerCaseEqualsASCII(*it, part))
+ return true;
+ }
+ return false;
}
bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict,
@@ -84,6 +88,9 @@ const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
"scope=%s&client_id=%s";
// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2WithDeviceTypeBodyFormat[] =
+ "scope=%s&client_id=%s&device_type=chrome";
+// static
const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
"scope=%s&"
"grant_type=authorization_code&"
@@ -110,21 +117,15 @@ const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
// static
const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
-const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel";
// static
const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
-const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis";
// static
const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
-const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth";
// static
const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
-const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr";
// static
const char GaiaAuthFetcher::kServiceUnavailableError[] =
"ServiceUnavailable";
-const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] =
- "ire";
// static
const char GaiaAuthFetcher::kErrorParam[] = "Error";
// static
@@ -156,10 +157,12 @@ const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
"Authorization: Bearer %s";
// static
-const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "Secure";
+const char GaiaAuthFetcher::kDeviceIdHeaderFormat[] = "X-Device-ID: %s";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
// static
const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
- "HttpOnly";
+ "httponly";
// static
const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
"oauth_code=";
@@ -298,21 +301,27 @@ std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
}
// static
-std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
+std::string GaiaAuthFetcher::MakeGetAuthCodeBody(bool include_device_type) {
std::string encoded_scope = net::EscapeUrlEncodedData(
- GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ GaiaConstants::kOAuth1LoginScope, true);
std::string encoded_client_id = net::EscapeUrlEncodedData(
GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
- return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
- encoded_scope.c_str(),
- encoded_client_id.c_str());
+ if (include_device_type) {
+ return base::StringPrintf(kClientLoginToOAuth2WithDeviceTypeBodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str());
+ } else {
+ return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str());
+ }
}
// static
std::string GaiaAuthFetcher::MakeGetTokenPairBody(
const std::string& auth_code) {
std::string encoded_scope = net::EscapeUrlEncodedData(
- GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ GaiaConstants::kOAuth1LoginScope, true);
std::string encoded_client_id = net::EscapeUrlEncodedData(
GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
std::string encoded_client_secret = net::EscapeUrlEncodedData(
@@ -516,7 +525,7 @@ void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
DVLOG(1) << "Starting OAuth login token exchange with auth_token";
- request_body_ = MakeGetAuthCodeBody();
+ request_body_ = MakeGetAuthCodeBody(false);
client_login_to_oauth2_gurl_ =
GaiaUrls::GetInstance()->client_login_to_oauth2_url();
@@ -547,10 +556,17 @@ void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
const std::string& session_index) {
+ StartCookieForOAuthLoginTokenExchangeWithDeviceId(session_index,
+ std::string());
+}
+
+void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchangeWithDeviceId(
+ const std::string& session_index,
+ const std::string& device_id) {
DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
- request_body_ = MakeGetAuthCodeBody();
+ request_body_ = MakeGetAuthCodeBody(!device_id.empty());
client_login_to_oauth2_gurl_ =
GaiaUrls::GetInstance()->client_login_to_oauth2_url();
@@ -559,9 +575,15 @@ void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
}
+ std::string device_id_header;
+ if (!device_id.empty()) {
+ device_id_header =
+ base::StringPrintf(kDeviceIdHeaderFormat, device_id.c_str());
+ }
+
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
- std::string(),
+ device_id_header,
client_login_to_oauth2_gurl_,
net::LOAD_NORMAL,
this));
@@ -680,102 +702,42 @@ GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
if (!status.is_success()) {
if (status.status() == net::URLRequestStatus::CANCELED) {
return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
- } else {
- DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
- << status.error();
- return GoogleServiceAuthError::FromConnectionError(status.error());
- }
- } else {
- if (IsSecondFactorSuccess(data)) {
- return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
- }
-
- std::string error;
- std::string url;
- std::string captcha_url;
- std::string captcha_token;
- ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
- DLOG(WARNING) << "ClientLogin failed with " << error;
-
- if (error == kCaptchaError) {
- GURL image_url(
- GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
- GURL unlock_url(url);
- return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
- captcha_token, image_url, unlock_url);
- }
- if (error == kAccountDeletedError)
- return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
- if (error == kAccountDisabledError)
- return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
- if (error == kBadAuthenticationError) {
- return GoogleServiceAuthError(
- GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
- }
- if (error == kServiceUnavailableError) {
- return GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_UNAVAILABLE);
}
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
- DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
+ if (IsSecondFactorSuccess(data))
+ return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
+
+ std::string error;
+ std::string url;
+ std::string captcha_url;
+ std::string captcha_token;
+ ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
+ DLOG(WARNING) << "ClientLogin failed with " << error;
+
+ if (error == kCaptchaError) {
+ return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
+ captcha_token,
+ GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)),
+ GURL(url));
+ }
+ if (error == kAccountDeletedError)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
+ if (error == kAccountDisabledError)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
+ if (error == kBadAuthenticationError) {
return GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
}
-
- NOTREACHED();
- return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
-}
-
-// static
-GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError(
- const std::string& data,
- const net::URLRequestStatus& status) {
- if (!status.is_success()) {
- if (status.status() == net::URLRequestStatus::CANCELED) {
- return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
- } else {
- DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
- << status.error();
- return GoogleServiceAuthError::FromConnectionError(status.error());
- }
- } else {
- if (IsSecondFactorSuccess(data)) {
- return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
- }
-
- std::string error;
- std::string url;
- std::string captcha_url;
- std::string captcha_token;
- ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
- LOG(WARNING) << "OAuthLogin failed with " << error;
-
- if (error == kCaptchaErrorCode) {
- GURL image_url(
- GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
- GURL unlock_url(url);
- return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
- captcha_token, image_url, unlock_url);
- }
- if (error == kAccountDeletedErrorCode)
- return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
- if (error == kAccountDisabledErrorCode)
- return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
- if (error == kBadAuthenticationErrorCode) {
- return GoogleServiceAuthError(
- GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
- }
- if (error == kServiceUnavailableErrorCode) {
- return GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_UNAVAILABLE);
- }
-
- DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
+ if (error == kServiceUnavailableError) {
return GoogleServiceAuthError(
GoogleServiceAuthError::SERVICE_UNAVAILABLE);
}
- NOTREACHED();
+ DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
}
@@ -819,7 +781,8 @@ void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
ParseClientLoginToOAuth2Response(cookies, &auth_code);
StartAuthCodeForOAuth2TokenExchange(auth_code);
} else {
- consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
+ GoogleServiceAuthError auth_error(GenerateAuthError(data, status));
+ consumer_->OnClientOAuthFailure(auth_error);
}
}
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher.h b/chromium/google_apis/gaia/gaia_auth_fetcher.h
index 0864f187cfb..aa66a49e032 100644
--- a/chromium/google_apis/gaia/gaia_auth_fetcher.h
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher.h
@@ -115,6 +115,19 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
// called on the consumer on the original thread.
void StartCookieForOAuthLoginTokenExchange(const std::string& session_index);
+ // Start a request to exchange the cookies of a signed-in user session
+ // for an OAuthLogin-scoped oauth2 token. In the case of a session with
+ // multiple accounts signed in, |session_index| indicate the which of accounts
+ // within the session.
+ // Resulting refresh token is annotated on the server with |device_id|. Format
+ // of device_id on the server is at most 64 unicode characters.
+ //
+ // Either OnClientOAuthSuccess or OnClientOAuthFailure will be
+ // called on the consumer on the original thread.
+ void StartCookieForOAuthLoginTokenExchangeWithDeviceId(
+ const std::string& session_index,
+ const std::string& device_id);
+
// Start a request to exchange the authorization code for an OAuthLogin-scoped
// oauth2 token.
//
@@ -191,6 +204,9 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
static const char kIssueAuthTokenFormat[];
// The format of the POST body to get OAuth2 auth code from auth token.
static const char kClientLoginToOAuth2BodyFormat[];
+ // The format of the POST body to get OAuth2 auth code from auth token. This
+ // format is used for request annotated with device_id.
+ static const char kClientLoginToOAuth2WithDeviceTypeBodyFormat[];
// The format of the POST body to get OAuth2 token pair from auth code.
static const char kOAuth2CodeToTokenPairBodyFormat[];
// The format of the POST body to revoke an OAuth2 token.
@@ -229,6 +245,7 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
static const char kAuthHeaderFormat[];
static const char kOAuthHeaderFormat[];
static const char kOAuth2BearerHeaderFormat[];
+ static const char kDeviceIdHeaderFormat[];
static const char kClientLoginToOAuth2CookiePartSecure[];
static const char kClientLoginToOAuth2CookiePartHttpOnly[];
static const char kClientLoginToOAuth2CookiePartCodePrefix[];
@@ -314,7 +331,7 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
const std::string& lsid,
const char* const service);
// Create body to get OAuth2 auth code.
- static std::string MakeGetAuthCodeBody();
+ static std::string MakeGetAuthCodeBody(bool include_device_type);
// Given auth code, create body to get OAuth2 token pair.
static std::string MakeGetTokenPairBody(const std::string& auth_code);
// Given an OAuth2 token, create body to revoke the token.
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc
index a12fb97b220..75a8db73afe 100644
--- a/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -402,7 +402,7 @@ TEST_F(GaiaAuthFetcherTest, AccountDisabledError) {
EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DISABLED);
}
-TEST_F(GaiaAuthFetcherTest,BadAuthenticationError) {
+TEST_F(GaiaAuthFetcherTest, BadAuthenticationError) {
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
std::string data = "Error=BadAuthentication\n";
GoogleServiceAuthError error =
@@ -410,7 +410,7 @@ TEST_F(GaiaAuthFetcherTest,BadAuthenticationError) {
EXPECT_EQ(error.state(), GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
}
-TEST_F(GaiaAuthFetcherTest,IncomprehensibleError) {
+TEST_F(GaiaAuthFetcherTest, IncomprehensibleError) {
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
std::string data = "Error=Gobbledygook\n";
GoogleServiceAuthError error =
@@ -418,43 +418,11 @@ TEST_F(GaiaAuthFetcherTest,IncomprehensibleError) {
EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
}
-TEST_F(GaiaAuthFetcherTest,ServiceUnavailableError) {
+TEST_F(GaiaAuthFetcherTest, ServiceUnavailableError) {
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
std::string data = "Error=ServiceUnavailable\n";
GoogleServiceAuthError error =
- GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
- EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
-}
-
-TEST_F(GaiaAuthFetcherTest, OAuthAccountDeletedError) {
- net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
- std::string data = "Error=adel\n";
- GoogleServiceAuthError error =
- GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
- EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DELETED);
-}
-
-TEST_F(GaiaAuthFetcherTest, OAuthAccountDisabledError) {
- net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
- std::string data = "Error=adis\n";
- GoogleServiceAuthError error =
- GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
- EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DISABLED);
-}
-
-TEST_F(GaiaAuthFetcherTest, OAuthBadAuthenticationError) {
- net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
- std::string data = "Error=badauth\n";
- GoogleServiceAuthError error =
- GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
- EXPECT_EQ(error.state(), GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-}
-
-TEST_F(GaiaAuthFetcherTest, OAuthServiceUnavailableError) {
- net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
- std::string data = "Error=ire\n";
- GoogleServiceAuthError error =
- GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ GaiaAuthFetcher::GenerateAuthError(data, status);
EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
}
@@ -601,6 +569,25 @@ TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenWithCookies) {
net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
EXPECT_TRUE(NULL != fetcher);
EXPECT_EQ(net::LOAD_NORMAL, fetcher->GetLoadFlags());
+ EXPECT_FALSE(EndsWith(fetcher->upload_data(), "device_type=chrome", true));
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenWithCookies_DeviceId) {
+ MockGaiaConsumer consumer;
+ net::TestURLFetcherFactory factory;
+ std::string expected_device_id("ABCDE-12345");
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartCookieForOAuthLoginTokenExchangeWithDeviceId("0",
+ expected_device_id);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ EXPECT_TRUE(NULL != fetcher);
+ EXPECT_EQ(net::LOAD_NORMAL, fetcher->GetLoadFlags());
+ EXPECT_TRUE(EndsWith(fetcher->upload_data(), "device_type=chrome", true));
+ net::HttpRequestHeaders extra_request_headers;
+ fetcher->GetExtraRequestHeaders(&extra_request_headers);
+ std::string device_id;
+ EXPECT_TRUE(extra_request_headers.GetHeader("X-Device-ID", &device_id));
+ EXPECT_EQ(device_id, expected_device_id);
}
TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenClientLoginToOAuth2Failure) {
diff --git a/chromium/google_apis/gaia/gaia_auth_util.cc b/chromium/google_apis/gaia/gaia_auth_util.cc
index f8f95c14f5b..287cd469827 100644
--- a/chromium/google_apis/gaia/gaia_auth_util.cc
+++ b/chromium/google_apis/gaia/gaia_auth_util.cc
@@ -15,22 +15,41 @@
namespace gaia {
namespace {
+
const char kGmailDomain[] = "gmail.com";
-}
+const char kGooglemailDomain[] = "googlemail.com";
-std::string CanonicalizeEmail(const std::string& email_address) {
+std::string CanonicalizeEmailImpl(const std::string& email_address,
+ bool change_googlemail_to_gmail) {
std::vector<std::string> parts;
char at = '@';
base::SplitString(email_address, at, &parts);
- if (parts.size() != 2U)
- NOTREACHED() << "expecting exactly one @, but got " << parts.size();
- else if (parts[1] == kGmailDomain) // only strip '.' for gmail accounts.
- base::RemoveChars(parts[0], ".", &parts[0]);
+ if (parts.size() != 2U) {
+ NOTREACHED() << "expecting exactly one @, but got " << parts.size()-1 <<
+ " : " << email_address;
+ } else {
+ if (change_googlemail_to_gmail && parts[1] == kGooglemailDomain)
+ parts[1] = kGmailDomain;
+
+ if (parts[1] == kGmailDomain) // only strip '.' for gmail accounts.
+ base::RemoveChars(parts[0], ".", &parts[0]);
+ }
+
std::string new_email = StringToLowerASCII(JoinString(parts, at));
VLOG(1) << "Canonicalized " << email_address << " to " << new_email;
return new_email;
}
+} // namespace
+
+std::string CanonicalizeEmail(const std::string& email_address) {
+ // CanonicalizeEmail() is called to process email strings that are eventually
+ // shown to the user, and may also be used in persisting email strings. To
+ // avoid breaking this existing behavior, this function will not try to
+ // change googlemail to gmail.
+ return CanonicalizeEmailImpl(email_address, false);
+}
+
std::string CanonicalizeDomain(const std::string& domain) {
// Canonicalization of domain names means lower-casing them. Make sure to
// update this function in sync with Canonicalize if this ever changes.
@@ -50,8 +69,8 @@ std::string SanitizeEmail(const std::string& email_address) {
}
bool AreEmailsSame(const std::string& email1, const std::string& email2) {
- return gaia::CanonicalizeEmail(gaia::SanitizeEmail(email1)) ==
- gaia::CanonicalizeEmail(gaia::SanitizeEmail(email2));
+ return CanonicalizeEmailImpl(gaia::SanitizeEmail(email1), true) ==
+ CanonicalizeEmailImpl(gaia::SanitizeEmail(email2), true);
}
std::string ExtractDomainName(const std::string& email_address) {
@@ -73,35 +92,48 @@ bool IsGaiaSignonRealm(const GURL& url) {
}
-std::vector<std::string> ParseListAccountsData(const std::string& data) {
- std::vector<std::string> account_ids;
+bool ParseListAccountsData(
+ const std::string& data,
+ std::vector<std::pair<std::string, bool> >* accounts) {
+ accounts->clear();
// Parse returned data and make sure we have data.
scoped_ptr<base::Value> value(base::JSONReader::Read(data));
if (!value)
- return account_ids;
+ return false;
base::ListValue* list;
if (!value->GetAsList(&list) || list->GetSize() < 2)
- return account_ids;
+ return false;
// Get list of account info.
- base::ListValue* accounts;
- if (!list->GetList(1, &accounts) || accounts == NULL)
- return account_ids;
+ base::ListValue* account_list;
+ if (!list->GetList(1, &account_list) || accounts == NULL)
+ return false;
// Build a vector of accounts from the cookie. Order is important: the first
// account in the list is the primary account.
- for (size_t i = 0; i < accounts->GetSize(); ++i) {
+ for (size_t i = 0; i < account_list->GetSize(); ++i) {
base::ListValue* account;
- if (accounts->GetList(i, &account) && account != NULL) {
+ if (account_list->GetList(i, &account) && account != NULL) {
std::string email;
- if (account->GetString(3, &email) && !email.empty())
- account_ids.push_back(email);
+ // Canonicalize the email since ListAccounts returns "display email".
+ if (account->GetString(3, &email) && !email.empty()) {
+ // New version if ListAccounts indicates whether the email's session
+ // is still valid or not. If this value is present and false, assume
+ // its invalid. Otherwise assume it's valid to remain compatible with
+ // old version.
+ int is_email_valid = 1;
+ if (!account->GetInteger(9, &is_email_valid))
+ is_email_valid = 1;
+
+ accounts->push_back(
+ std::make_pair(CanonicalizeEmail(email), is_email_valid != 0));
+ }
}
}
- return account_ids;
+ return true;
}
} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_auth_util.h b/chromium/google_apis/gaia/gaia_auth_util.h
index 354d116e7a5..d28f54698bb 100644
--- a/chromium/google_apis/gaia/gaia_auth_util.h
+++ b/chromium/google_apis/gaia/gaia_auth_util.h
@@ -6,6 +6,7 @@
#define GOOGLE_APIS_GAIA_GAIA_AUTH_UTIL_H_
#include <string>
+#include <utility>
#include <vector>
class GURL;
@@ -13,10 +14,7 @@ class GURL;
namespace gaia {
// Perform basic canonicalization of |email_address|, taking into account that
-// gmail does not consider '.' or caps inside a username to matter. It also
-// ignores everything after a '+'. For example, c.masone+abc@gmail.com ==
-// cMaSone@gmail.com, per
-// http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313#
+// gmail does not consider '.' or caps inside a username to matter.
std::string CanonicalizeEmail(const std::string& email_address);
// Returns the canonical form of the given domain.
@@ -35,9 +33,13 @@ std::string ExtractDomainName(const std::string& email);
bool IsGaiaSignonRealm(const GURL& url);
-// Parses JSON data returned by /ListAccounts call, returns vector of
-// accounts (email addresses).
-std::vector<std::string> ParseListAccountsData(const std::string& data);
+// Parses JSON data returned by /ListAccounts call, returning a vector of
+// email/valid pairs. An email addresses is considered valid if a passive
+// login would succeed (i.e. the user does not need to reauthenticate).
+// If there an error parsing the JSON, then false is returned.
+bool ParseListAccountsData(
+ const std::string& data,
+ std::vector<std::pair<std::string, bool> >* accounts);
} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_auth_util_unittest.cc b/chromium/google_apis/gaia/gaia_auth_util_unittest.cc
index ebe8f876abe..b17ac57516a 100644
--- a/chromium/google_apis/gaia/gaia_auth_util_unittest.cc
+++ b/chromium/google_apis/gaia/gaia_auth_util_unittest.cc
@@ -54,14 +54,9 @@ TEST(GaiaAuthUtilTest, EmailAddressDifferentOnesRejected) {
CanonicalizeEmail("Us....E.r@what.com"));
}
-TEST(GaiaAuthUtilTest, EmailAddressIgnorePlusSuffix) {
- const char with_plus[] = "user+cc@what.com";
- EXPECT_EQ(with_plus, CanonicalizeEmail(with_plus));
-}
-
-TEST(GaiaAuthUtilTest, EmailAddressIgnoreMultiPlusSuffix) {
- const char multi_plus[] = "user+cc+bcc@what.com";
- EXPECT_EQ(multi_plus, CanonicalizeEmail(multi_plus));
+TEST(GaiaAuthUtilTest, GooglemailNotCanonicalizedToGmail) {
+ const char googlemail[] = "user@googlemail.com";
+ EXPECT_EQ(googlemail, CanonicalizeEmail(googlemail));
}
TEST(GaiaAuthUtilTest, CanonicalizeDomain) {
@@ -93,6 +88,11 @@ TEST(GaiaAuthUtilTest, AreEmailsSame) {
EXPECT_FALSE(AreEmailsSame("user@gmail.com", "foo@gmail.com"));
}
+TEST(GaiaAuthUtilTest, GmailAndGooglemailAreSame) {
+ EXPECT_TRUE(AreEmailsSame("foo@gmail.com", "foo@googlemail.com"));
+ EXPECT_FALSE(AreEmailsSame("bar@gmail.com", "foo@googlemail.com"));
+}
+
TEST(GaiaAuthUtilTest, IsGaiaSignonRealm) {
// Only https versions of Gaia URLs should be considered valid.
EXPECT_TRUE(IsGaiaSignonRealm(GURL("https://accounts.google.com/")));
@@ -109,37 +109,79 @@ TEST(GaiaAuthUtilTest, IsGaiaSignonRealm) {
}
TEST(GaiaAuthUtilTest, ParseListAccountsData) {
- std::vector<std::string> accounts;
- accounts = ParseListAccountsData("");
+ std::vector<std::pair<std::string, bool> > accounts;
+ ASSERT_FALSE(ParseListAccountsData("", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData("1");
+ ASSERT_FALSE(ParseListAccountsData("1", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData("[]");
+ ASSERT_FALSE(ParseListAccountsData("[]", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData("[\"foo\", \"bar\"]");
+ ASSERT_FALSE(ParseListAccountsData("[\"foo\", \"bar\"]", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData("[\"foo\", []]");
+ ASSERT_TRUE(ParseListAccountsData("[\"foo\", []]", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData(
- "[\"foo\", [[\"bar\", 0, \"name\", 0, \"photo\", 0, 0, 0]]]");
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"bar\", 0, \"name\", 0, \"photo\", 0, 0, 0]]]", &accounts));
ASSERT_EQ(0u, accounts.size());
- accounts = ParseListAccountsData(
- "[\"foo\", [[\"bar\", 0, \"name\", \"email\", \"photo\", 0, 0, 0]]]");
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"bar\", 0, \"name\", \"u@g.c\", \"photo\", 0, 0, 0]]]",
+ &accounts));
ASSERT_EQ(1u, accounts.size());
- ASSERT_EQ("email", accounts[0]);
+ ASSERT_EQ("u@g.c", accounts[0].first);
+ ASSERT_TRUE(accounts[0].second);
- accounts = ParseListAccountsData(
- "[\"foo\", [[\"bar1\", 0, \"name1\", \"email1\", \"photo1\", 0, 0, 0], "
- "[\"bar2\", 0, \"name2\", \"email2\", \"photo2\", 0, 0, 0]]]");
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"bar1\", 0, \"name1\", \"u1@g.c\", \"photo1\", 0, 0, 0], "
+ "[\"bar2\", 0, \"name2\", \"u2@g.c\", \"photo2\", 0, 0, 0]]]",
+ &accounts));
ASSERT_EQ(2u, accounts.size());
- ASSERT_EQ("email1", accounts[0]);
- ASSERT_EQ("email2", accounts[1]);
+ ASSERT_EQ("u1@g.c", accounts[0].first);
+ ASSERT_TRUE(accounts[0].second);
+ ASSERT_EQ("u2@g.c", accounts[1].first);
+ ASSERT_TRUE(accounts[1].second);
+
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"b1\", 0, \"name1\", \"U1@g.c\", \"photo1\", 0, 0, 0], "
+ "[\"b2\", 0, \"name2\", \"u.2@g.c\", \"photo2\", 0, 0, 0]]]",
+ &accounts));
+ ASSERT_EQ(2u, accounts.size());
+ ASSERT_EQ(CanonicalizeEmail("U1@g.c"), accounts[0].first);
+ ASSERT_TRUE(accounts[0].second);
+ ASSERT_EQ(CanonicalizeEmail("u.2@g.c"), accounts[1].first);
+ ASSERT_TRUE(accounts[1].second);
+}
+
+TEST(GaiaAuthUtilTest, ParseListAccountsDataValidSession) {
+ std::vector<std::pair<std::string, bool> > accounts;
+
+ // Missing valid session means: return account.
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"b\", 0, \"n\", \"u@g.c\", \"p\", 0, 0, 0]]]",
+ &accounts));
+ ASSERT_EQ(1u, accounts.size());
+ ASSERT_EQ("u@g.c", accounts[0].first);
+ ASSERT_TRUE(accounts[0].second);
+
+ // Valid session is true means: return account.
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"b\", 0, \"n\", \"u@g.c\", \"p\", 0, 0, 0, 0, 1]]]",
+ &accounts));
+ ASSERT_EQ(1u, accounts.size());
+ ASSERT_EQ("u@g.c", accounts[0].first);
+ ASSERT_TRUE(accounts[0].second);
+
+ // Valid session is false means: return account with valid bit false.
+ ASSERT_TRUE(ParseListAccountsData(
+ "[\"foo\", [[\"b\", 0, \"n\", \"u@g.c\", \"p\", 0, 0, 0, 0, 0]]]",
+ &accounts));
+ ASSERT_EQ(1u, accounts.size());
+ ASSERT_FALSE(accounts[0].second);
}
} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_constants.cc b/chromium/google_apis/gaia/gaia_constants.cc
index 1b1581d1545..95a5945f74e 100644
--- a/chromium/google_apis/gaia/gaia_constants.cc
+++ b/chromium/google_apis/gaia/gaia_constants.cc
@@ -23,6 +23,11 @@ const char kSyncService[] = "chromiumsync";
// Service name for remoting.
const char kRemotingService[] = "chromoting";
+// OAuth scopes.
+const char kOAuth1LoginScope[] = "https://www.google.com/accounts/OAuthLogin";
+const char kOAuthWrapBridgeUserInfoScope[] =
+ "https://www.googleapis.com/auth/userinfo.email";
+
// Service/scope names for device management (cloud-based policy) server.
const char kDeviceManagementServiceOAuth[] =
"https://www.googleapis.com/auth/chromeosdevicemanagement";
@@ -34,7 +39,7 @@ const char kAnyApiOAuth2Scope[] = "https://www.googleapis.com/auth/any-api";
const char kChromeSyncOAuth2Scope[] =
"https://www.googleapis.com/auth/chromesync";
// OAuth2 scope for access to the Chrome Sync APIs for managed profiles.
-const char kChromeSyncManagedOAuth2Scope[] =
+const char kChromeSyncSupervisedOAuth2Scope[] =
"https://www.googleapis.com/auth/chromesync_playpen";
// OAuth2 scope for access to Google Talk APIs (XMPP).
const char kGoogleTalkOAuth2Scope[] =
diff --git a/chromium/google_apis/gaia/gaia_constants.h b/chromium/google_apis/gaia/gaia_constants.h
index 3b45eee0dcd..ea8cc0f2f2c 100644
--- a/chromium/google_apis/gaia/gaia_constants.h
+++ b/chromium/google_apis/gaia/gaia_constants.h
@@ -18,10 +18,12 @@ extern const char kGaiaService[]; // uber token
extern const char kPicasaService[];
extern const char kSyncService[];
extern const char kRemotingService[];
+extern const char kOAuth1LoginScope[];
+extern const char kOAuthWrapBridgeUserInfoScope[];
extern const char kDeviceManagementServiceOAuth[];
extern const char kAnyApiOAuth2Scope[];
extern const char kChromeSyncOAuth2Scope[];
-extern const char kChromeSyncManagedOAuth2Scope[];
+extern const char kChromeSyncSupervisedOAuth2Scope[];
extern const char kGoogleTalkOAuth2Scope[];
// Used with uber auth tokens when needed.
diff --git a/chromium/google_apis/gaia/gaia_oauth_client.cc b/chromium/google_apis/gaia/gaia_oauth_client.cc
index 8c6e1807369..1113ff67309 100644
--- a/chromium/google_apis/gaia/gaia_oauth_client.cc
+++ b/chromium/google_apis/gaia/gaia_oauth_client.cc
@@ -241,9 +241,12 @@ void GaiaOAuthClient::Core::HandleResponse(
scoped_ptr<net::URLFetcher> old_request = request_.Pass();
DCHECK_EQ(source, old_request.get());
- // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
+ // HTTP_BAD_REQUEST means the arguments are invalid. HTTP_UNAUTHORIZED means
+ // the access or refresh token is invalid. No point retrying. We are
// done here.
- if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) {
+ int response_code = source->GetResponseCode();
+ if (response_code == net::HTTP_BAD_REQUEST ||
+ response_code == net::HTTP_UNAUTHORIZED) {
delegate_->OnOAuthError();
return;
}
diff --git a/chromium/google_apis/gaia/gaia_oauth_client.h b/chromium/google_apis/gaia/gaia_oauth_client.h
index 14a26a63d7e..8e01ef6a83a 100644
--- a/chromium/google_apis/gaia/gaia_oauth_client.h
+++ b/chromium/google_apis/gaia/gaia_oauth_client.h
@@ -49,7 +49,7 @@ class GaiaOAuthClient {
virtual void OnGetUserIdResponse(const std::string& user_id) {}
// Invoked on a successful response to the GetTokenInfo request.
virtual void OnGetTokenInfoResponse(
- scoped_ptr<DictionaryValue> token_info) {}
+ scoped_ptr<base::DictionaryValue> token_info) {}
// Invoked when there is an OAuth error with one of the requests.
virtual void OnOAuthError() = 0;
// Invoked when there is a network error or upon receiving an invalid
diff --git a/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc b/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc
index 32e7c6620b4..d4014f73679 100644
--- a/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc
+++ b/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc
@@ -199,15 +199,15 @@ class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
// override the problematic method to call through to it.
// https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ
MOCK_METHOD1(OnGetTokenInfoResponsePtr,
- void(const DictionaryValue* token_info));
- virtual void OnGetTokenInfoResponse(scoped_ptr<DictionaryValue> token_info)
- OVERRIDE {
+ void(const base::DictionaryValue* token_info));
+ virtual void OnGetTokenInfoResponse(
+ scoped_ptr<base::DictionaryValue> token_info) OVERRIDE {
token_info_.reset(token_info.release());
OnGetTokenInfoResponsePtr(token_info_.get());
}
private:
- scoped_ptr<DictionaryValue> token_info_;
+ scoped_ptr<base::DictionaryValue> token_info_;
DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate);
};
@@ -328,7 +328,7 @@ TEST_F(GaiaOAuthClientTest, GetUserId) {
}
TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
- const DictionaryValue* captured_result;
+ const base::DictionaryValue* captured_result;
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_))
diff --git a/chromium/google_apis/gaia/gaia_switches.cc b/chromium/google_apis/gaia/gaia_switches.cc
index 16228c3837b..a1879d1c994 100644
--- a/chromium/google_apis/gaia/gaia_switches.cc
+++ b/chromium/google_apis/gaia/gaia_switches.cc
@@ -6,12 +6,10 @@
namespace switches {
-const char kClientLoginToOAuth2Url[] = "client-login-to-oauth2-url";
const char kGaiaUrl[] = "gaia-url";
const char kGoogleApisUrl[] = "google-apis-url";
const char kLsoUrl[] = "lso-url";
-const char kOAuth1LoginScope[] = "oauth1-login-scope";
-const char kOAuthWrapBridgeUserInfoScope[] =
- "oauth-wrap-bridge-user-info-scope";
+const char kOAuth2ClientID[] = "oauth2-client-id";
+const char kOAuth2ClientSecret[] = "oauth2-client-secret";
} // namespace switches
diff --git a/chromium/google_apis/gaia/gaia_switches.h b/chromium/google_apis/gaia/gaia_switches.h
index 0700f36c70c..bc69ebe5bab 100644
--- a/chromium/google_apis/gaia/gaia_switches.h
+++ b/chromium/google_apis/gaia/gaia_switches.h
@@ -7,9 +7,6 @@
namespace switches {
-// Supplies custom client login to OAuth2 URL for testing purposes.
-extern const char kClientLoginToOAuth2Url[];
-
// Specifies the path for GAIA authentication URL. The default value is
// "https://accounts.google.com".
extern const char kGaiaUrl[];
@@ -22,11 +19,11 @@ extern const char kGoogleApisUrl[];
// "https://accounts.google.com".
extern const char kLsoUrl[];
-// Specifies custom OAuth1 login scope for testing purposes.
-extern const char kOAuth1LoginScope[];
+// Specifies custom OAuth2 client id for testing purposes.
+extern const char kOAuth2ClientID[];
-// Overrides OAuth wrap bridge user info scope.
-extern const char kOAuthWrapBridgeUserInfoScope[];
+// Specifies custom OAuth2 client secret for testing purposes.
+extern const char kOAuth2ClientSecret[];
} // namespace switches
diff --git a/chromium/google_apis/gaia/gaia_urls.cc b/chromium/google_apis/gaia/gaia_urls.cc
index 053f7a37e29..0b44a911c74 100644
--- a/chromium/google_apis/gaia/gaia_urls.cc
+++ b/chromium/google_apis/gaia/gaia_urls.cc
@@ -28,15 +28,10 @@ const char kOAuthGetAccessTokenUrlSuffix[] = "OAuthGetAccessToken";
const char kOAuthWrapBridgeUrlSuffix[] = "OAuthWrapBridge";
const char kOAuth1LoginUrlSuffix[] = "OAuthLogin";
const char kOAuthRevokeTokenUrlSuffix[] = "AuthSubRevokeToken";
-const char kListAccountsSuffix[] = "ListAccounts";
+const char kListAccountsSuffix[] = "ListAccounts?json=standard";
const char kEmbeddedSigninSuffix[] = "EmbeddedSignIn";
const char kAddAccountSuffix[] = "AddSession";
-// OAuth scopes
-const char kOAuth1LoginScope[] = "https://www.google.com/accounts/OAuthLogin";
-const char kOAuthWrapBridgeUserInfoScope[] =
- "https://www.googleapis.com/auth/userinfo.email";
-
// API calls from accounts.google.com (LSO)
const char kGetOAuthTokenUrlSuffix[] = "o/oauth/GetOAuthToken/";
const char kClientLoginToOAuth2UrlSuffix[] = "o/oauth2/programmatic_auth";
@@ -127,14 +122,6 @@ GaiaUrls::GaiaUrls() {
google_apis_origin_url_.Resolve(kOAuthUserInfoUrlSuffix);
gaia_login_form_realm_ = gaia_url_;
-
- // OAuth scopes.
- GetSwitchValueWithDefault(switches::kOAuthWrapBridgeUserInfoScope,
- kOAuthWrapBridgeUserInfoScope,
- &oauth_wrap_bridge_user_info_scope_);
- GetSwitchValueWithDefault(switches::kOAuth1LoginScope,
- kOAuth1LoginScope,
- &oauth1_login_scope_);
}
GaiaUrls::~GaiaUrls() {
@@ -216,14 +203,6 @@ const GURL& GaiaUrls::add_account_url() const {
return add_account_url_;
}
-const std::string& GaiaUrls::oauth1_login_scope() const {
- return oauth1_login_scope_;
-}
-
-const std::string& GaiaUrls::oauth_wrap_bridge_user_info_scope() const {
- return oauth_wrap_bridge_user_info_scope_;
-}
-
const std::string& GaiaUrls::oauth2_chrome_client_id() const {
return oauth2_chrome_client_id_;
}
diff --git a/chromium/google_apis/gaia/gaia_urls.h b/chromium/google_apis/gaia/gaia_urls.h
index 2ec6499663e..f96e89ce230 100644
--- a/chromium/google_apis/gaia/gaia_urls.h
+++ b/chromium/google_apis/gaia/gaia_urls.h
@@ -36,9 +36,6 @@ class GaiaUrls {
const GURL& embedded_signin_url() const;
const GURL& add_account_url() const;
- const std::string& oauth1_login_scope() const;
- const std::string& oauth_wrap_bridge_user_info_scope() const;
-
const std::string& oauth2_chrome_client_id() const;
const std::string& oauth2_chrome_client_secret() const;
const GURL& client_login_to_oauth2_url() const;
@@ -80,9 +77,6 @@ class GaiaUrls {
GURL embedded_signin_url_;
GURL add_account_url_;
- std::string oauth1_login_scope_;
- std::string oauth_wrap_bridge_user_info_scope_;
-
std::string oauth2_chrome_client_id_;
std::string oauth2_chrome_client_secret_;
diff --git a/chromium/google_apis/gaia/google_service_auth_error.cc b/chromium/google_apis/gaia/google_service_auth_error.cc
index 736bbd62785..d968136de0d 100644
--- a/chromium/google_apis/gaia/google_service_auth_error.cc
+++ b/chromium/google_apis/gaia/google_service_auth_error.cc
@@ -81,12 +81,6 @@ GoogleServiceAuthError::GoogleServiceAuthError(
error_message_(error_message) {
}
-GoogleServiceAuthError::GoogleServiceAuthError(const std::string& error_message)
- : state_(INVALID_GAIA_CREDENTIALS),
- network_error_(0),
- error_message_(error_message) {
-}
-
// static
GoogleServiceAuthError
GoogleServiceAuthError::FromConnectionError(int error) {
@@ -259,14 +253,3 @@ GoogleServiceAuthError::GoogleServiceAuthError(
captcha_unlock_url, image_width, image_height),
network_error_(0) {
}
-
-GoogleServiceAuthError::GoogleServiceAuthError(
- State s,
- const std::string& captcha_token,
- const std::string& prompt_text,
- const std::string& alternate_text,
- int field_length)
- : state_(s),
- second_factor_(captcha_token, prompt_text, alternate_text, field_length),
- network_error_(0) {
-}
diff --git a/chromium/google_apis/gaia/google_service_auth_error.h b/chromium/google_apis/gaia/google_service_auth_error.h
index 19fdb515405..3d441ef9bae 100644
--- a/chromium/google_apis/gaia/google_service_auth_error.h
+++ b/chromium/google_apis/gaia/google_service_auth_error.h
@@ -189,8 +189,6 @@ class GoogleServiceAuthError {
// Construct a GoogleServiceAuthError from |state| and |error_message|.
GoogleServiceAuthError(State state, const std::string& error_message);
- explicit GoogleServiceAuthError(const std::string& error_message);
-
GoogleServiceAuthError(State s, const std::string& captcha_token,
const GURL& captcha_audio_url,
const GURL& captcha_image_url,
@@ -198,11 +196,6 @@ class GoogleServiceAuthError {
int image_width,
int image_height);
- GoogleServiceAuthError(State s, const std::string& captcha_token,
- const std::string& prompt_text,
- const std::string& alternate_text,
- int field_length);
-
State state_;
Captcha captcha_;
SecondFactor second_factor_;
diff --git a/chromium/google_apis/gaia/google_service_auth_error_unittest.cc b/chromium/google_apis/gaia/google_service_auth_error_unittest.cc
index d1f920ab9c2..8e221cc631a 100644
--- a/chromium/google_apis/gaia/google_service_auth_error_unittest.cc
+++ b/chromium/google_apis/gaia/google_service_auth_error_unittest.cc
@@ -20,7 +20,7 @@ class GoogleServiceAuthErrorTest : public testing::Test {};
void TestSimpleState(GoogleServiceAuthError::State state) {
GoogleServiceAuthError error(state);
- scoped_ptr<DictionaryValue> value(error.ToValue());
+ scoped_ptr<base::DictionaryValue> value(error.ToValue());
EXPECT_EQ(1u, value->size());
std::string state_str;
EXPECT_TRUE(value->GetString("state", &state_str));
@@ -38,7 +38,7 @@ TEST_F(GoogleServiceAuthErrorTest, SimpleToValue) {
TEST_F(GoogleServiceAuthErrorTest, None) {
GoogleServiceAuthError error(GoogleServiceAuthError::AuthErrorNone());
- scoped_ptr<DictionaryValue> value(error.ToValue());
+ scoped_ptr<base::DictionaryValue> value(error.ToValue());
EXPECT_EQ(1u, value->size());
ExpectDictStringValue("NONE", *value, "state");
}
@@ -46,7 +46,7 @@ TEST_F(GoogleServiceAuthErrorTest, None) {
TEST_F(GoogleServiceAuthErrorTest, ConnectionFailed) {
GoogleServiceAuthError error(
GoogleServiceAuthError::FromConnectionError(net::OK));
- scoped_ptr<DictionaryValue> value(error.ToValue());
+ scoped_ptr<base::DictionaryValue> value(error.ToValue());
EXPECT_EQ(2u, value->size());
ExpectDictStringValue("CONNECTION_FAILED", *value, "state");
ExpectDictStringValue("net::OK", *value, "networkError");
diff --git a/chromium/google_apis/gaia/identity_provider.cc b/chromium/google_apis/gaia/identity_provider.cc
new file mode 100644
index 00000000000..842891e67f3
--- /dev/null
+++ b/chromium/google_apis/gaia/identity_provider.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 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 "google_apis/gaia/identity_provider.h"
+
+IdentityProvider::Observer::~Observer() {}
+
+IdentityProvider::~IdentityProvider() {}
+
+void IdentityProvider::AddActiveAccountRefreshTokenObserver(
+ OAuth2TokenService::Observer* observer) {
+ OAuth2TokenService* token_service = GetTokenService();
+ if (!token_service || token_service_observers_.HasObserver(observer))
+ return;
+
+ token_service_observers_.AddObserver(observer);
+ if (++token_service_observer_count_ == 1)
+ token_service->AddObserver(this);
+}
+
+void IdentityProvider::RemoveActiveAccountRefreshTokenObserver(
+ OAuth2TokenService::Observer* observer) {
+ OAuth2TokenService* token_service = GetTokenService();
+ if (!token_service || !token_service_observers_.HasObserver(observer))
+ return;
+
+ token_service_observers_.RemoveObserver(observer);
+ if (--token_service_observer_count_ == 0)
+ token_service->RemoveObserver(this);
+}
+
+void IdentityProvider::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void IdentityProvider::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void IdentityProvider::OnRefreshTokenAvailable(const std::string& account_id) {
+ if (account_id != GetActiveAccountId())
+ return;
+ FOR_EACH_OBSERVER(OAuth2TokenService::Observer,
+ token_service_observers_,
+ OnRefreshTokenAvailable(account_id));
+}
+
+void IdentityProvider::OnRefreshTokenRevoked(const std::string& account_id) {
+ if (account_id != GetActiveAccountId())
+ return;
+ FOR_EACH_OBSERVER(OAuth2TokenService::Observer,
+ token_service_observers_,
+ OnRefreshTokenRevoked(account_id));
+}
+
+void IdentityProvider::OnRefreshTokensLoaded() {
+ FOR_EACH_OBSERVER(OAuth2TokenService::Observer,
+ token_service_observers_,
+ OnRefreshTokensLoaded());
+}
+
+IdentityProvider::IdentityProvider() : token_service_observer_count_(0) {}
+
+void IdentityProvider::FireOnActiveAccountLogin() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnActiveAccountLogin());
+}
+
+void IdentityProvider::FireOnActiveAccountLogout() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnActiveAccountLogout());
+}
diff --git a/chromium/google_apis/gaia/identity_provider.h b/chromium/google_apis/gaia/identity_provider.h
new file mode 100644
index 00000000000..e0e99ee688c
--- /dev/null
+++ b/chromium/google_apis/gaia/identity_provider.h
@@ -0,0 +1,93 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_IDENTITY_PROVIDER_H_
+#define GOOGLE_APIS_GAIA_IDENTITY_PROVIDER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+// Helper class that provides access to information about logged-in GAIA
+// accounts. Each instance of this class references an entity who may be logged
+// in to zero, one or multiple GAIA accounts. The class provides access to the
+// OAuth tokens for all logged-in accounts and indicates which of these is
+// currently active.
+// The main purpose of this abstraction layer is to isolate consumers of GAIA
+// information from the different sources and various token service
+// implementations. Whenever possible, consumers of GAIA information should be
+// provided with an instance of this class instead of accessing other GAIA APIs
+// directly.
+class IdentityProvider : public OAuth2TokenService::Observer {
+ public:
+ class Observer {
+ public:
+ // Called when a GAIA account logs in and becomes the active account. All
+ // account information is available when this method is called and all
+ // |IdentityProvider| methods will return valid data.
+ virtual void OnActiveAccountLogin() {}
+
+ // Called when the active GAIA account logs out. The account information may
+ // have been cleared already when this method is called. The
+ // |IdentityProvider| methods may return inconsistent or outdated
+ // information if called from within OnLogout().
+ virtual void OnActiveAccountLogout() {}
+
+ protected:
+ virtual ~Observer();
+ };
+
+ virtual ~IdentityProvider();
+
+ // Adds and removes observers that will be notified of changes to the refresh
+ // token availability for the active account.
+ void AddActiveAccountRefreshTokenObserver(
+ OAuth2TokenService::Observer* observer);
+ void RemoveActiveAccountRefreshTokenObserver(
+ OAuth2TokenService::Observer* observer);
+
+ // Gets the active account's user name.
+ virtual std::string GetActiveUsername() = 0;
+
+ // Gets the active account's account ID.
+ virtual std::string GetActiveAccountId() = 0;
+
+ // Gets the token service vending OAuth tokens for all logged-in accounts.
+ virtual OAuth2TokenService* GetTokenService() = 0;
+
+ // Requests login to a GAIA account. Implementations can show a login UI, log
+ // in automatically if sufficient credentials are available or may ignore the
+ // request. Returns true if the login request was processed and false if it
+ // was ignored.
+ virtual bool RequestLogin() = 0;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // OAuth2TokenService::Observer:
+ virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE;
+ virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE;
+ virtual void OnRefreshTokensLoaded() OVERRIDE;
+
+ protected:
+ IdentityProvider();
+
+ // Fires an OnActiveAccountLogin notification.
+ void FireOnActiveAccountLogin();
+
+ // Fires an OnActiveAccountLogout notification.
+ void FireOnActiveAccountLogout();
+
+ private:
+ ObserverList<Observer, true> observers_;
+ ObserverList<OAuth2TokenService::Observer, true> token_service_observers_;
+ int token_service_observer_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(IdentityProvider);
+};
+
+#endif // GOOGLE_APIS_GAIA_IDENTITY_PROVIDER_H_
diff --git a/chromium/google_apis/gaia/merge_session_helper.cc b/chromium/google_apis/gaia/merge_session_helper.cc
new file mode 100644
index 00000000000..c3c8a2ead10
--- /dev/null
+++ b/chromium/google_apis/gaia/merge_session_helper.cc
@@ -0,0 +1,178 @@
+// Copyright 2014 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 "google_apis/gaia/merge_session_helper.h"
+
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+MergeSessionHelper::MergeSessionHelper(
+ OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context,
+ Observer* observer)
+ : token_service_(token_service),
+ request_context_(request_context) {
+ if (observer)
+ AddObserver(observer);
+}
+
+MergeSessionHelper::~MergeSessionHelper() {
+ DCHECK(accounts_.empty());
+}
+
+void MergeSessionHelper::LogIn(const std::string& account_id) {
+ DCHECK(!account_id.empty());
+ accounts_.push_back(account_id);
+ if (accounts_.size() == 1)
+ StartFetching();
+}
+
+void MergeSessionHelper::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void MergeSessionHelper::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void MergeSessionHelper::CancelAll() {
+ gaia_auth_fetcher_.reset();
+ uber_token_fetcher_.reset();
+ accounts_.clear();
+}
+
+void MergeSessionHelper::LogOut(
+ const std::string& account_id,
+ const std::vector<std::string>& accounts) {
+ DCHECK(!account_id.empty());
+ LogOutInternal(account_id, accounts);
+}
+
+void MergeSessionHelper::LogOutInternal(
+ const std::string& account_id,
+ const std::vector<std::string>& accounts) {
+ bool pending = !accounts_.empty();
+
+ if (pending) {
+ for (std::deque<std::string>::const_iterator it = accounts_.begin() + 1;
+ it != accounts_.end(); it++) {
+ if (!it->empty() &&
+ (std::find(accounts.begin(), accounts.end(), *it) == accounts.end() ||
+ *it == account_id)) {
+ // We have a pending log in request for an account followed by
+ // a signout.
+ GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
+ SignalComplete(*it, error);
+ }
+ }
+
+ // Remove every thing in the work list besides the one that is running.
+ accounts_.resize(1);
+ }
+
+ // Signal a logout to be the next thing to do unless the pending
+ // action is already a logout.
+ if (!pending || !accounts_.front().empty())
+ accounts_.push_back("");
+
+ for (std::vector<std::string>::const_iterator it = accounts.begin();
+ it != accounts.end(); it++) {
+ if (*it != account_id) {
+ DCHECK(!it->empty());
+ accounts_.push_back(*it);
+ }
+ }
+
+ if (!pending)
+ StartLogOutUrlFetch();
+}
+
+void MergeSessionHelper::LogOutAllAccounts() {
+ LogOutInternal("", std::vector<std::string>());
+}
+
+void MergeSessionHelper::SignalComplete(
+ const std::string& account_id,
+ const GoogleServiceAuthError& error) {
+ // Its possible for the observer to delete |this| object. Don't access
+ // access any members after this calling the observer. This method should
+ // be the last call in any other method.
+ FOR_EACH_OBSERVER(Observer, observer_list_,
+ MergeSessionCompleted(account_id, error));
+}
+
+void MergeSessionHelper::StartLogOutUrlFetch() {
+ DCHECK(accounts_.front().empty());
+ GURL logout_url(GaiaUrls::GetInstance()->service_logout_url());
+ net::URLFetcher* fetcher =
+ net::URLFetcher::Create(logout_url, net::URLFetcher::GET, this);
+ fetcher->SetRequestContext(request_context_);
+ fetcher->Start();
+}
+
+void MergeSessionHelper::OnUbertokenSuccess(const std::string& uber_token) {
+ VLOG(1) << "MergeSessionHelper::OnUbertokenSuccess"
+ << " account=" << accounts_.front();
+ gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this,
+ GaiaConstants::kChromeSource,
+ request_context_));
+ gaia_auth_fetcher_->StartMergeSession(uber_token);
+}
+
+void MergeSessionHelper::OnUbertokenFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "Failed to retrieve ubertoken"
+ << " account=" << accounts_.front()
+ << " error=" << error.ToString();
+ const std::string account_id = accounts_.front();
+ HandleNextAccount();
+ SignalComplete(account_id, error);
+}
+
+void MergeSessionHelper::OnMergeSessionSuccess(const std::string& data) {
+ DVLOG(1) << "MergeSession successful account=" << accounts_.front();
+ const std::string account_id = accounts_.front();
+ HandleNextAccount();
+ SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone());
+}
+
+void MergeSessionHelper::OnMergeSessionFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "Failed MergeSession"
+ << " account=" << accounts_.front()
+ << " error=" << error.ToString();
+ const std::string account_id = accounts_.front();
+ HandleNextAccount();
+ SignalComplete(account_id, error);
+}
+
+void MergeSessionHelper::StartFetching() {
+ uber_token_fetcher_.reset(new UbertokenFetcher(token_service_,
+ this,
+ request_context_));
+ uber_token_fetcher_->StartFetchingToken(accounts_.front());
+}
+
+void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(accounts_.front().empty());
+ HandleNextAccount();
+}
+
+void MergeSessionHelper::HandleNextAccount() {
+ accounts_.pop_front();
+ gaia_auth_fetcher_.reset();
+ if (accounts_.empty()) {
+ uber_token_fetcher_.reset();
+ } else {
+ if (accounts_.front().empty()) {
+ StartLogOutUrlFetch();
+ } else {
+ StartFetching();
+ }
+ }
+}
diff --git a/chromium/google_apis/gaia/merge_session_helper.h b/chromium/google_apis/gaia/merge_session_helper.h
new file mode 100644
index 00000000000..f4af8f04132
--- /dev/null
+++ b/chromium/google_apis/gaia/merge_session_helper.h
@@ -0,0 +1,117 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_MERGE_SESSION_HELPER_H_
+#define GOOGLE_APIS_GAIA_MERGE_SESSION_HELPER_H_
+
+#include <deque>
+
+#include "base/observer_list.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/ubertoken_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+class GaiaAuthFetcher;
+class GoogleServiceAuthError;
+class OAuth2TokenService;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Merges a Google account known to Chrome into the cookie jar. When merging
+// multiple accounts, one instance of the helper is better than multiple
+// instances if there is the possibility that they run concurrently, since
+// changes to the cookie must be serialized.
+//
+// By default instances of MergeSessionHelper delete themselves when done.
+class MergeSessionHelper : public GaiaAuthConsumer,
+ public UbertokenConsumer,
+ public net::URLFetcherDelegate {
+ public:
+ class Observer {
+ public:
+ // Called whenever a merge session is completed. The account that was
+ // merged is given by |account_id|. If |error| is equal to
+ // GoogleServiceAuthError::AuthErrorNone() then the merge succeeeded.
+ virtual void MergeSessionCompleted(const std::string& account_id,
+ const GoogleServiceAuthError& error) = 0;
+ protected:
+ virtual ~Observer() {}
+ };
+
+ MergeSessionHelper(OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context,
+ Observer* observer);
+ virtual ~MergeSessionHelper();
+
+ void LogIn(const std::string& account_id);
+
+ // Add or remove observers of this helper.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Cancel all login requests.
+ void CancelAll();
+
+ // Signout of |account_id| given a list of accounts already signed in.
+ // Since this involves signing out of all accounts and resigning back in,
+ // the order which |accounts| are given is important as it will dictate
+ // the sign in order. |account_id| does not have to be in |accounts|.
+ void LogOut(const std::string& account_id,
+ const std::vector<std::string>& accounts);
+
+ // Signout all accounts.
+ void LogOutAllAccounts();
+
+ // Call observers when merge session completes. This public so that callers
+ // that know that a given account is already in the cookie jar can simply
+ // inform the observers.
+ void SignalComplete(const std::string& account_id,
+ const GoogleServiceAuthError& error);
+
+ private:
+ // Overridden from UbertokenConsumer.
+ virtual void OnUbertokenSuccess(const std::string& token) OVERRIDE;
+ virtual void OnUbertokenFailure(const GoogleServiceAuthError& error) OVERRIDE;
+
+ // Overridden from GaiaAuthConsumer.
+ virtual void OnMergeSessionSuccess(const std::string& data) OVERRIDE;
+ virtual void OnMergeSessionFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+
+ void LogOutInternal(const std::string& account_id,
+ const std::vector<std::string>& accounts);
+
+ // Starts the proess of fetching the uber token and performing a merge session
+ // for the next account. Virtual so that it can be overriden in tests.
+ virtual void StartFetching();
+
+ // Virtual for testing purpose.
+ virtual void StartLogOutUrlFetch();
+
+ // Start the next merge session, if needed.
+ void HandleNextAccount();
+
+ // Overridden from URLFetcherDelgate.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ OAuth2TokenService* token_service_;
+ net::URLRequestContextGetter* request_context_;
+ scoped_ptr<GaiaAuthFetcher> gaia_auth_fetcher_;
+ scoped_ptr<UbertokenFetcher> uber_token_fetcher_;
+
+ // A worklist for this class. Accounts names are stored here if
+ // we are pending a signin action for that account. Empty strings
+ // represent a signout request.
+ std::deque<std::string> accounts_;
+
+ // List of observers to notify when merge session completes.
+ // Makes sure list is empty on destruction.
+ ObserverList<Observer, true> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(MergeSessionHelper);
+};
+
+#endif // GOOGLE_APIS_GAIA_MERGE_SESSION_HELPER_H_
diff --git a/chromium/google_apis/gaia/merge_session_helper_unittest.cc b/chromium/google_apis/gaia/merge_session_helper_unittest.cc
new file mode 100644
index 00000000000..1fbe8a6898a
--- /dev/null
+++ b/chromium/google_apis/gaia/merge_session_helper_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright 2014 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 <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/merge_session_helper.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockObserver : public MergeSessionHelper::Observer {
+ public:
+ explicit MockObserver(MergeSessionHelper* helper) : helper_(helper) {
+ helper_->AddObserver(this);
+ }
+
+ ~MockObserver() {
+ helper_->RemoveObserver(this);
+ }
+
+ MOCK_METHOD2(MergeSessionCompleted,
+ void(const std::string&,
+ const GoogleServiceAuthError& ));
+ private:
+ MergeSessionHelper* helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockObserver);
+};
+
+// Counts number of InstrumentedMergeSessionHelper created.
+// We can EXPECT_* to be zero at the end of our unit tests
+// to make sure everything is properly deleted.
+
+int total = 0;
+
+class InstrumentedMergeSessionHelper : public MergeSessionHelper {
+ public:
+ InstrumentedMergeSessionHelper(
+ OAuth2TokenService* token_service,
+ net::URLRequestContextGetter* request_context) :
+ MergeSessionHelper(token_service, request_context, NULL) {
+ total++;
+ }
+
+ virtual ~InstrumentedMergeSessionHelper() {
+ total--;
+ }
+
+ MOCK_METHOD0(StartFetching, void());
+ MOCK_METHOD0(StartLogOutUrlFetch, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InstrumentedMergeSessionHelper);
+};
+
+class MergeSessionHelperTest : public testing::Test {
+ public:
+ MergeSessionHelperTest()
+ : no_error_(GoogleServiceAuthError::NONE),
+ error_(GoogleServiceAuthError::SERVICE_ERROR),
+ canceled_(GoogleServiceAuthError::REQUEST_CANCELED),
+ request_context_getter_(new net::TestURLRequestContextGetter(
+ base::MessageLoopProxy::current())) {}
+
+ OAuth2TokenService* token_service() { return &token_service_; }
+ net::URLRequestContextGetter* request_context() {
+ return request_context_getter_;
+ }
+
+ void SimulateUbertokenFailure(UbertokenConsumer* consumer,
+ const GoogleServiceAuthError& error) {
+ consumer->OnUbertokenFailure(error);
+ }
+
+ void SimulateMergeSessionSuccess(GaiaAuthConsumer* consumer,
+ const std::string& data) {
+ consumer->OnMergeSessionSuccess(data);
+ }
+
+ void SimulateMergeSessionFailure(GaiaAuthConsumer* consumer,
+ const GoogleServiceAuthError& error) {
+ consumer->OnMergeSessionFailure(error);
+ }
+
+ void SimulateLogoutSuccess(net::URLFetcherDelegate* consumer) {
+ consumer->OnURLFetchComplete(NULL);
+ }
+
+ const GoogleServiceAuthError& no_error() { return no_error_; }
+ const GoogleServiceAuthError& error() { return error_; }
+ const GoogleServiceAuthError& canceled() { return canceled_; }
+
+ private:
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory factory_;
+ FakeOAuth2TokenService token_service_;
+ GoogleServiceAuthError no_error_;
+ GoogleServiceAuthError error_;
+ GoogleServiceAuthError canceled_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+};
+
+} // namespace
+
+using ::testing::_;
+
+TEST_F(MergeSessionHelperTest, Success) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching());
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", no_error()));
+
+ helper.LogIn("acc1@gmail.com");
+ SimulateMergeSessionSuccess(&helper, "token");
+}
+
+TEST_F(MergeSessionHelperTest, FailedMergeSession) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching());
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", error()));
+
+ helper.LogIn("acc1@gmail.com");
+ SimulateMergeSessionFailure(&helper, error());
+}
+
+TEST_F(MergeSessionHelperTest, FailedUbertoken) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching());
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", error()));
+
+ helper.LogIn("acc1@gmail.com");
+ SimulateUbertokenFailure(&helper, error());
+}
+
+TEST_F(MergeSessionHelperTest, ContinueAfterSuccess) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", no_error()));
+ EXPECT_CALL(observer, MergeSessionCompleted("acc2@gmail.com", no_error()));
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogIn("acc2@gmail.com");
+ SimulateMergeSessionSuccess(&helper, "token1");
+ SimulateMergeSessionSuccess(&helper, "token2");
+}
+
+TEST_F(MergeSessionHelperTest, ContinueAfterFailure1) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", error()));
+ EXPECT_CALL(observer, MergeSessionCompleted("acc2@gmail.com", no_error()));
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogIn("acc2@gmail.com");
+ SimulateMergeSessionFailure(&helper, error());
+ SimulateMergeSessionSuccess(&helper, "token2");
+}
+
+TEST_F(MergeSessionHelperTest, ContinueAfterFailure2) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", error()));
+ EXPECT_CALL(observer, MergeSessionCompleted("acc2@gmail.com", no_error()));
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogIn("acc2@gmail.com");
+ SimulateUbertokenFailure(&helper, error());
+ SimulateMergeSessionSuccess(&helper, "token2");
+}
+
+TEST_F(MergeSessionHelperTest, AllRequestsInMultipleGoes) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ EXPECT_CALL(helper, StartFetching()).Times(4);
+ EXPECT_CALL(observer, MergeSessionCompleted(_, no_error())).Times(4);
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogIn("acc2@gmail.com");
+
+ SimulateMergeSessionSuccess(&helper, "token1");
+
+ helper.LogIn("acc3@gmail.com");
+
+ SimulateMergeSessionSuccess(&helper, "token2");
+ SimulateMergeSessionSuccess(&helper, "token3");
+
+ helper.LogIn("acc4@gmail.com");
+
+ SimulateMergeSessionSuccess(&helper, "token4");
+}
+
+TEST_F(MergeSessionHelperTest, LogOut) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ std::vector<std::string> current_accounts;
+ current_accounts.push_back("acc1@gmail.com");
+ current_accounts.push_back("acc2@gmail.com");
+ current_accounts.push_back("acc3@gmail.com");
+
+ EXPECT_CALL(helper, StartLogOutUrlFetch());
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", no_error()));
+ EXPECT_CALL(observer, MergeSessionCompleted("acc3@gmail.com", no_error()));
+
+ helper.LogOut("acc2@gmail.com", current_accounts);
+ SimulateLogoutSuccess(&helper);
+ SimulateMergeSessionSuccess(&helper, "token1");
+ SimulateMergeSessionSuccess(&helper, "token3");
+}
+
+TEST_F(MergeSessionHelperTest, PendingSigninThenSignout) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ std::vector<std::string> current_accounts;
+ current_accounts.push_back("acc2@gmail.com");
+ current_accounts.push_back("acc3@gmail.com");
+
+ // From the first Signin.
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", no_error()));
+
+ // From the sign out and then re-sign in.
+ EXPECT_CALL(helper, StartLogOutUrlFetch());
+ EXPECT_CALL(observer, MergeSessionCompleted("acc3@gmail.com", no_error()));
+
+ // Total sign in 2 times, not enforcing ordered sequences.
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogOut("acc2@gmail.com", current_accounts);
+
+ SimulateMergeSessionSuccess(&helper, "token1");
+ SimulateLogoutSuccess(&helper);
+ SimulateMergeSessionSuccess(&helper, "token3");
+}
+
+TEST_F(MergeSessionHelperTest, CancelSignIn) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ std::vector<std::string> current_accounts;
+
+ EXPECT_CALL(helper, StartFetching());
+ EXPECT_CALL(observer, MergeSessionCompleted("acc2@gmail.com", canceled()));
+ EXPECT_CALL(observer, MergeSessionCompleted("acc1@gmail.com", no_error()));
+ EXPECT_CALL(helper, StartLogOutUrlFetch());
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogIn("acc2@gmail.com");
+ helper.LogOut("acc2@gmail.com", current_accounts);
+
+ SimulateMergeSessionSuccess(&helper, "token1");
+ SimulateLogoutSuccess(&helper);
+}
+
+TEST_F(MergeSessionHelperTest, DoubleSignout) {
+ InstrumentedMergeSessionHelper helper(token_service(), request_context());
+ MockObserver observer(&helper);
+
+ std::vector<std::string> current_accounts1;
+ current_accounts1.push_back("acc1@gmail.com");
+ current_accounts1.push_back("acc2@gmail.com");
+ current_accounts1.push_back("acc3@gmail.com");
+
+ std::vector<std::string> current_accounts2;
+ current_accounts2.push_back("acc1@gmail.com");
+ current_accounts2.push_back("acc3@gmail.com");
+
+ EXPECT_CALL(helper, StartFetching()).Times(2);
+ EXPECT_CALL(observer, MergeSessionCompleted("acc3@gmail.com", canceled()));
+ EXPECT_CALL(observer,
+ MergeSessionCompleted("acc1@gmail.com", no_error())).Times(2);
+ EXPECT_CALL(helper, StartLogOutUrlFetch());
+
+ helper.LogIn("acc1@gmail.com");
+ helper.LogOut("acc2@gmail.com", current_accounts1);
+ helper.LogOut("acc3@gmail.com", current_accounts2);
+
+ SimulateMergeSessionSuccess(&helper, "token1");
+ SimulateLogoutSuccess(&helper);
+ SimulateMergeSessionSuccess(&helper, "token1");
+}
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc
index ab382056d4b..12e464c67ff 100644
--- a/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc
@@ -4,315 +4,19 @@
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
-#include <algorithm>
-#include <string>
-#include <vector>
-
-#include "base/json/json_reader.h"
-#include "base/metrics/histogram.h"
-#include "base/metrics/sparse_histogram.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/time/time.h"
-#include "base/values.h"
-#include "google_apis/gaia/gaia_urls.h"
-#include "google_apis/gaia/google_service_auth_error.h"
-#include "net/base/escape.h"
-#include "net/base/load_flags.h"
-#include "net/http/http_status_code.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "net/url_request/url_request_status.h"
-
-using net::ResponseCookies;
-using net::URLFetcher;
-using net::URLFetcherDelegate;
-using net::URLRequestContextGetter;
-using net::URLRequestStatus;
-
-namespace {
-static const char kGetAccessTokenBodyFormat[] =
- "client_id=%s&"
- "client_secret=%s&"
- "grant_type=refresh_token&"
- "refresh_token=%s";
-
-static const char kGetAccessTokenBodyWithScopeFormat[] =
- "client_id=%s&"
- "client_secret=%s&"
- "grant_type=refresh_token&"
- "refresh_token=%s&"
- "scope=%s";
-
-static const char kAccessTokenKey[] = "access_token";
-static const char kExpiresInKey[] = "expires_in";
-static const char kErrorKey[] = "error";
-
-// Enumerated constants for logging server responses on 400 errors, matching
-// RFC 6749.
-enum OAuth2ErrorCodesForHistogram {
- OAUTH2_ACCESS_ERROR_INVALID_REQUEST = 0,
- OAUTH2_ACCESS_ERROR_INVALID_CLIENT,
- OAUTH2_ACCESS_ERROR_INVALID_GRANT,
- OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT,
- OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE,
- OAUTH2_ACCESS_ERROR_INVALID_SCOPE,
- OAUTH2_ACCESS_ERROR_UNKNOWN,
- OAUTH2_ACCESS_ERROR_COUNT
-};
-
-OAuth2ErrorCodesForHistogram OAuth2ErrorToHistogramValue(
- const std::string& error) {
- if (error == "invalid_request")
- return OAUTH2_ACCESS_ERROR_INVALID_REQUEST;
- else if (error == "invalid_client")
- return OAUTH2_ACCESS_ERROR_INVALID_CLIENT;
- else if (error == "invalid_grant")
- return OAUTH2_ACCESS_ERROR_INVALID_GRANT;
- else if (error == "unauthorized_client")
- return OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT;
- else if (error == "unsupported_grant_type")
- return OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE;
- else if (error == "invalid_scope")
- return OAUTH2_ACCESS_ERROR_INVALID_SCOPE;
-
- return OAUTH2_ACCESS_ERROR_UNKNOWN;
-}
-
-static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
- CHECK(!status.is_success());
- if (status.status() == URLRequestStatus::CANCELED) {
- return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
- } else {
- DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
- << status.error();
- return GoogleServiceAuthError::FromConnectionError(status.error());
- }
-}
-
-static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
- const GURL& url,
- const std::string& body,
- URLFetcherDelegate* delegate) {
- bool empty_body = body.empty();
- URLFetcher* result = net::URLFetcher::Create(
- 0, url,
- empty_body ? URLFetcher::GET : URLFetcher::POST,
- delegate);
-
- result->SetRequestContext(getter);
- result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
- net::LOAD_DO_NOT_SAVE_COOKIES);
- // Fetchers are sometimes cancelled because a network change was detected,
- // especially at startup and after sign-in on ChromeOS. Retrying once should
- // be enough in those cases; let the fetcher retry up to 3 times just in case.
- // http://crbug.com/163710
- result->SetAutomaticallyRetryOnNetworkChanges(3);
-
- if (!empty_body)
- result->SetUploadData("application/x-www-form-urlencoded", body);
-
- return result;
-}
-} // namespace
-
OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
- OAuth2AccessTokenConsumer* consumer,
- URLRequestContextGetter* getter)
- : consumer_(consumer),
- getter_(getter),
- state_(INITIAL) { }
-
-OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
+ OAuth2AccessTokenConsumer* consumer)
+ : consumer_(consumer) {}
-void OAuth2AccessTokenFetcher::CancelRequest() {
- fetcher_.reset();
-}
-
-void OAuth2AccessTokenFetcher::Start(const std::string& client_id,
- const std::string& client_secret,
- const std::string& refresh_token,
- const std::vector<std::string>& scopes) {
- client_id_ = client_id;
- client_secret_ = client_secret;
- refresh_token_ = refresh_token;
- scopes_ = scopes;
- StartGetAccessToken();
-}
-
-void OAuth2AccessTokenFetcher::StartGetAccessToken() {
- CHECK_EQ(INITIAL, state_);
- state_ = GET_ACCESS_TOKEN_STARTED;
- fetcher_.reset(CreateFetcher(
- getter_,
- MakeGetAccessTokenUrl(),
- MakeGetAccessTokenBody(
- client_id_, client_secret_, refresh_token_, scopes_),
- this));
- fetcher_->Start(); // OnURLFetchComplete will be called.
-}
+OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() {}
-void OAuth2AccessTokenFetcher::EndGetAccessToken(
- const net::URLFetcher* source) {
- CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
- state_ = GET_ACCESS_TOKEN_DONE;
-
- URLRequestStatus status = source->GetStatus();
- int histogram_value = status.is_success() ? source->GetResponseCode() :
- status.error();
- UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken",
- histogram_value);
- if (!status.is_success()) {
- OnGetTokenFailure(CreateAuthError(status));
- return;
- }
-
- switch (source->GetResponseCode()) {
- case net::HTTP_OK:
- break;
- case net::HTTP_FORBIDDEN:
- case net::HTTP_INTERNAL_SERVER_ERROR:
- // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
- // '403 Rate Limit Exeeded.' 500 is always treated as transient.
- OnGetTokenFailure(GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_UNAVAILABLE));
- return;
- case net::HTTP_BAD_REQUEST: {
- // HTTP_BAD_REQUEST (400) usually contains error as per
- // http://tools.ietf.org/html/rfc6749#section-5.2.
- std::string gaia_error;
- if (!ParseGetAccessTokenFailureResponse(source, &gaia_error)) {
- OnGetTokenFailure(GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_ERROR));
- return;
- }
-
- OAuth2ErrorCodesForHistogram access_error(OAuth2ErrorToHistogramValue(
- gaia_error));
- UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
- access_error, OAUTH2_ACCESS_ERROR_COUNT);
-
- OnGetTokenFailure(access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT ?
- GoogleServiceAuthError(
- GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) :
- GoogleServiceAuthError(
- GoogleServiceAuthError::SERVICE_ERROR));
- return;
- }
- default:
- // The other errors are treated as permanent error.
- OnGetTokenFailure(GoogleServiceAuthError(
- GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
- return;
- }
-
- // The request was successfully fetched and it returned OK.
- // Parse out the access token and the expiration time.
- std::string access_token;
- int expires_in;
- if (!ParseGetAccessTokenSuccessResponse(
- source, &access_token, &expires_in)) {
- DLOG(WARNING) << "Response doesn't match expected format";
- OnGetTokenFailure(
- GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
- return;
- }
- // The token will expire in |expires_in| seconds. Take a 10% error margin to
- // prevent reusing a token too close to its expiration date.
- OnGetTokenSuccess(
- access_token,
- base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
-}
-
-void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
+void OAuth2AccessTokenFetcher::FireOnGetTokenSuccess(
const std::string& access_token,
const base::Time& expiration_time) {
consumer_->OnGetTokenSuccess(access_token, expiration_time);
}
-void OAuth2AccessTokenFetcher::OnGetTokenFailure(
+void OAuth2AccessTokenFetcher::FireOnGetTokenFailure(
const GoogleServiceAuthError& error) {
- state_ = ERROR_STATE;
consumer_->OnGetTokenFailure(error);
}
-
-void OAuth2AccessTokenFetcher::OnURLFetchComplete(
- const net::URLFetcher* source) {
- CHECK(source);
- CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
- EndGetAccessToken(source);
-}
-
-// static
-GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
- return GaiaUrls::GetInstance()->oauth2_token_url();
-}
-
-// static
-std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
- const std::string& client_id,
- const std::string& client_secret,
- const std::string& refresh_token,
- const std::vector<std::string>& scopes) {
- std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
- std::string enc_client_secret =
- net::EscapeUrlEncodedData(client_secret, true);
- std::string enc_refresh_token =
- net::EscapeUrlEncodedData(refresh_token, true);
- if (scopes.empty()) {
- return base::StringPrintf(
- kGetAccessTokenBodyFormat,
- enc_client_id.c_str(),
- enc_client_secret.c_str(),
- enc_refresh_token.c_str());
- } else {
- std::string scopes_string = JoinString(scopes, ' ');
- return base::StringPrintf(
- kGetAccessTokenBodyWithScopeFormat,
- enc_client_id.c_str(),
- enc_client_secret.c_str(),
- enc_refresh_token.c_str(),
- net::EscapeUrlEncodedData(scopes_string, true).c_str());
- }
-}
-
-scoped_ptr<base::DictionaryValue> ParseGetAccessTokenResponse(
- const net::URLFetcher* source) {
- CHECK(source);
-
- std::string data;
- source->GetResponseAsString(&data);
- scoped_ptr<base::Value> value(base::JSONReader::Read(data));
- if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
- value.reset();
-
- return scoped_ptr<base::DictionaryValue>(
- static_cast<base::DictionaryValue*>(value.release()));
-}
-
-// static
-bool OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse(
- const net::URLFetcher* source,
- std::string* access_token,
- int* expires_in) {
- CHECK(access_token);
- scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(
- source);
- if (value.get() == NULL)
- return false;
-
- return value->GetString(kAccessTokenKey, access_token) &&
- value->GetInteger(kExpiresInKey, expires_in);
-}
-
-// static
-bool OAuth2AccessTokenFetcher::ParseGetAccessTokenFailureResponse(
- const net::URLFetcher* source,
- std::string* error) {
- CHECK(error);
- scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(
- source);
- if (value.get() == NULL)
- return false;
- return value->GetString(kErrorKey, error);
-}
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher.h b/chromium/google_apis/gaia/oauth2_access_token_fetcher.h
index 90805c09963..cd3ee5c7ba4 100644
--- a/chromium/google_apis/gaia/oauth2_access_token_fetcher.h
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher.h
@@ -8,33 +8,12 @@
#include <string>
#include <vector>
-#include "base/gtest_prod_util.h"
-#include "base/memory/scoped_ptr.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
#include "net/url_request/url_fetcher_delegate.h"
-#include "url/gurl.h"
-class OAuth2AccessTokenFetcherTest;
+class OAuth2AccessTokenConsumer;
-namespace base {
-class Time;
-}
-
-namespace net {
-class URLFetcher;
-class URLRequestContextGetter;
-class URLRequestStatus;
-}
-
-// Abstracts the details to get OAuth2 access token token from
-// OAuth2 refresh token.
-// See "Using the Refresh Token" section in:
-// http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
-//
-// This class should be used on a single thread, but it can be whichever thread
-// that you like.
-// Also, do not reuse the same instance. Once Start() is called, the instance
-// should not be reused.
+// Interface of a OAuth2 access token fetcher.
//
// Usage:
// * Create an instance with a consumer.
@@ -44,10 +23,9 @@ class URLRequestStatus;
//
// This class can handle one request at a time. To parallelize requests,
// create multiple instances.
-class OAuth2AccessTokenFetcher : public net::URLFetcherDelegate {
+class OAuth2AccessTokenFetcher {
public:
- OAuth2AccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
- net::URLRequestContextGetter* getter);
+ explicit OAuth2AccessTokenFetcher(OAuth2AccessTokenConsumer* consumer);
virtual ~OAuth2AccessTokenFetcher();
// Starts the flow with the given parameters.
@@ -58,65 +36,21 @@ class OAuth2AccessTokenFetcher : public net::URLFetcherDelegate {
// a super-set of the specified scopes.
virtual void Start(const std::string& client_id,
const std::string& client_secret,
- const std::string& refresh_token,
- const std::vector<std::string>& scopes);
-
- void CancelRequest();
-
- // Implementation of net::URLFetcherDelegate
- virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
-
- private:
- enum State {
- INITIAL,
- GET_ACCESS_TOKEN_STARTED,
- GET_ACCESS_TOKEN_DONE,
- ERROR_STATE,
- };
-
- // Helper methods for the flow.
- void StartGetAccessToken();
- void EndGetAccessToken(const net::URLFetcher* source);
-
- // Helper mehtods for reporting back results.
- void OnGetTokenSuccess(const std::string& access_token,
- const base::Time& expiration_time);
- void OnGetTokenFailure(const GoogleServiceAuthError& error);
+ const std::vector<std::string>& scopes) = 0;
- // Other helpers.
- static GURL MakeGetAccessTokenUrl();
- static std::string MakeGetAccessTokenBody(
- const std::string& client_id,
- const std::string& client_secret,
- const std::string& refresh_token,
- const std::vector<std::string>& scopes);
+ // Cancels the current request and informs the consumer.
+ virtual void CancelRequest() = 0;
- static bool ParseGetAccessTokenSuccessResponse(
- const net::URLFetcher* source,
- std::string* access_token,
- int* expires_in);
+ protected:
+ // Fires |OnGetTokenSuccess| on |consumer_|.
+ void FireOnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time);
- static bool ParseGetAccessTokenFailureResponse(
- const net::URLFetcher* source,
- std::string* error);
+ // Fires |OnGetTokenFailure| on |consumer_|.
+ void FireOnGetTokenFailure(const GoogleServiceAuthError& error);
- // State that is set during construction.
+ private:
OAuth2AccessTokenConsumer* const consumer_;
- net::URLRequestContextGetter* const getter_;
- State state_;
-
- // While a fetch is in progress.
- scoped_ptr<net::URLFetcher> fetcher_;
- std::string client_id_;
- std::string client_secret_;
- std::string refresh_token_;
- std::vector<std::string> scopes_;
-
- friend class OAuth2AccessTokenFetcherTest;
- FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherTest,
- ParseGetAccessTokenResponse);
- FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherTest,
- MakeGetAccessTokenBody);
DISALLOW_COPY_AND_ASSIGN(OAuth2AccessTokenFetcher);
};
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
new file mode 100644
index 00000000000..f4cd4f04f1e
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
@@ -0,0 +1,313 @@
+// Copyright 2014 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 "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using net::ResponseCookies;
+using net::URLFetcher;
+using net::URLFetcherDelegate;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+namespace {
+static const char kGetAccessTokenBodyFormat[] =
+ "client_id=%s&"
+ "client_secret=%s&"
+ "grant_type=refresh_token&"
+ "refresh_token=%s";
+
+static const char kGetAccessTokenBodyWithScopeFormat[] =
+ "client_id=%s&"
+ "client_secret=%s&"
+ "grant_type=refresh_token&"
+ "refresh_token=%s&"
+ "scope=%s";
+
+static const char kAccessTokenKey[] = "access_token";
+static const char kExpiresInKey[] = "expires_in";
+static const char kErrorKey[] = "error";
+
+// Enumerated constants for logging server responses on 400 errors, matching
+// RFC 6749.
+enum OAuth2ErrorCodesForHistogram {
+ OAUTH2_ACCESS_ERROR_INVALID_REQUEST = 0,
+ OAUTH2_ACCESS_ERROR_INVALID_CLIENT,
+ OAUTH2_ACCESS_ERROR_INVALID_GRANT,
+ OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT,
+ OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE,
+ OAUTH2_ACCESS_ERROR_INVALID_SCOPE,
+ OAUTH2_ACCESS_ERROR_UNKNOWN,
+ OAUTH2_ACCESS_ERROR_COUNT
+};
+
+OAuth2ErrorCodesForHistogram OAuth2ErrorToHistogramValue(
+ const std::string& error) {
+ if (error == "invalid_request")
+ return OAUTH2_ACCESS_ERROR_INVALID_REQUEST;
+ else if (error == "invalid_client")
+ return OAUTH2_ACCESS_ERROR_INVALID_CLIENT;
+ else if (error == "invalid_grant")
+ return OAUTH2_ACCESS_ERROR_INVALID_GRANT;
+ else if (error == "unauthorized_client")
+ return OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT;
+ else if (error == "unsupported_grant_type")
+ return OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE;
+ else if (error == "invalid_scope")
+ return OAUTH2_ACCESS_ERROR_INVALID_SCOPE;
+
+ return OAUTH2_ACCESS_ERROR_UNKNOWN;
+}
+
+static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
+ CHECK(!status.is_success());
+ if (status.status() == URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ } else {
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+}
+
+static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
+ const GURL& url,
+ const std::string& body,
+ URLFetcherDelegate* delegate) {
+ bool empty_body = body.empty();
+ URLFetcher* result = net::URLFetcher::Create(
+ 0, url, empty_body ? URLFetcher::GET : URLFetcher::POST, delegate);
+
+ result->SetRequestContext(getter);
+ result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ // Fetchers are sometimes cancelled because a network change was detected,
+ // especially at startup and after sign-in on ChromeOS. Retrying once should
+ // be enough in those cases; let the fetcher retry up to 3 times just in case.
+ // http://crbug.com/163710
+ result->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ if (!empty_body)
+ result->SetUploadData("application/x-www-form-urlencoded", body);
+
+ return result;
+}
+
+scoped_ptr<base::DictionaryValue> ParseGetAccessTokenResponse(
+ const net::URLFetcher* source) {
+ CHECK(source);
+
+ std::string data;
+ source->GetResponseAsString(&data);
+ scoped_ptr<base::Value> value(base::JSONReader::Read(data));
+ if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
+ value.reset();
+
+ return scoped_ptr<base::DictionaryValue>(
+ static_cast<base::DictionaryValue*>(value.release()));
+}
+
+} // namespace
+
+OAuth2AccessTokenFetcherImpl::OAuth2AccessTokenFetcherImpl(
+ OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter,
+ const std::string& refresh_token)
+ : OAuth2AccessTokenFetcher(consumer),
+ getter_(getter),
+ refresh_token_(refresh_token),
+ state_(INITIAL) {}
+
+OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() {}
+
+void OAuth2AccessTokenFetcherImpl::CancelRequest() { fetcher_.reset(); }
+
+void OAuth2AccessTokenFetcherImpl::Start(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::vector<std::string>& scopes) {
+ client_id_ = client_id;
+ client_secret_ = client_secret;
+ scopes_ = scopes;
+ StartGetAccessToken();
+}
+
+void OAuth2AccessTokenFetcherImpl::StartGetAccessToken() {
+ CHECK_EQ(INITIAL, state_);
+ state_ = GET_ACCESS_TOKEN_STARTED;
+ fetcher_.reset(
+ CreateFetcher(getter_,
+ MakeGetAccessTokenUrl(),
+ MakeGetAccessTokenBody(
+ client_id_, client_secret_, refresh_token_, scopes_),
+ this));
+ fetcher_->Start(); // OnURLFetchComplete will be called.
+}
+
+void OAuth2AccessTokenFetcherImpl::EndGetAccessToken(
+ const net::URLFetcher* source) {
+ CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
+ state_ = GET_ACCESS_TOKEN_DONE;
+
+ URLRequestStatus status = source->GetStatus();
+ int histogram_value =
+ status.is_success() ? source->GetResponseCode() : status.error();
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken",
+ histogram_value);
+ if (!status.is_success()) {
+ OnGetTokenFailure(CreateAuthError(status));
+ return;
+ }
+
+ switch (source->GetResponseCode()) {
+ case net::HTTP_OK:
+ break;
+ case net::HTTP_FORBIDDEN:
+ case net::HTTP_INTERNAL_SERVER_ERROR:
+ // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
+ // '403 Rate Limit Exeeded.' 500 is always treated as transient.
+ OnGetTokenFailure(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
+ return;
+ case net::HTTP_BAD_REQUEST: {
+ // HTTP_BAD_REQUEST (400) usually contains error as per
+ // http://tools.ietf.org/html/rfc6749#section-5.2.
+ std::string gaia_error;
+ if (!ParseGetAccessTokenFailureResponse(source, &gaia_error)) {
+ OnGetTokenFailure(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
+ return;
+ }
+
+ OAuth2ErrorCodesForHistogram access_error(
+ OAuth2ErrorToHistogramValue(gaia_error));
+ UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
+ access_error,
+ OAUTH2_ACCESS_ERROR_COUNT);
+
+ OnGetTokenFailure(
+ access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT
+ ? GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)
+ : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
+ return;
+ }
+ default:
+ // The other errors are treated as permanent error.
+ OnGetTokenFailure(GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
+ return;
+ }
+
+ // The request was successfully fetched and it returned OK.
+ // Parse out the access token and the expiration time.
+ std::string access_token;
+ int expires_in;
+ if (!ParseGetAccessTokenSuccessResponse(source, &access_token, &expires_in)) {
+ DLOG(WARNING) << "Response doesn't match expected format";
+ OnGetTokenFailure(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
+ return;
+ }
+ // The token will expire in |expires_in| seconds. Take a 10% error margin to
+ // prevent reusing a token too close to its expiration date.
+ OnGetTokenSuccess(
+ access_token,
+ base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
+}
+
+void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess(
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ FireOnGetTokenSuccess(access_token, expiration_time);
+}
+
+void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure(
+ const GoogleServiceAuthError& error) {
+ state_ = ERROR_STATE;
+ FireOnGetTokenFailure(error);
+}
+
+void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ CHECK(source);
+ CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
+ EndGetAccessToken(source);
+}
+
+// static
+GURL OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() {
+ return GaiaUrls::GetInstance()->oauth2_token_url();
+}
+
+// static
+std::string OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes) {
+ std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
+ std::string enc_client_secret =
+ net::EscapeUrlEncodedData(client_secret, true);
+ std::string enc_refresh_token =
+ net::EscapeUrlEncodedData(refresh_token, true);
+ if (scopes.empty()) {
+ return base::StringPrintf(kGetAccessTokenBodyFormat,
+ enc_client_id.c_str(),
+ enc_client_secret.c_str(),
+ enc_refresh_token.c_str());
+ } else {
+ std::string scopes_string = JoinString(scopes, ' ');
+ return base::StringPrintf(
+ kGetAccessTokenBodyWithScopeFormat,
+ enc_client_id.c_str(),
+ enc_client_secret.c_str(),
+ enc_refresh_token.c_str(),
+ net::EscapeUrlEncodedData(scopes_string, true).c_str());
+ }
+}
+
+// static
+bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
+ const net::URLFetcher* source,
+ std::string* access_token,
+ int* expires_in) {
+ CHECK(access_token);
+ scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source);
+ if (value.get() == NULL)
+ return false;
+
+ return value->GetString(kAccessTokenKey, access_token) &&
+ value->GetInteger(kExpiresInKey, expires_in);
+}
+
+// static
+bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
+ const net::URLFetcher* source,
+ std::string* error) {
+ CHECK(error);
+ scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source);
+ if (value.get() == NULL)
+ return false;
+ return value->GetString(kErrorKey, error);
+}
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.h b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.h
new file mode 100644
index 00000000000..8839b6c13cb
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl.h
@@ -0,0 +1,118 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_IMPL_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+class OAuth2AccessTokenFetcherImplTest;
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+class URLRequestStatus;
+}
+
+// Abstracts the details to get OAuth2 access token token from
+// OAuth2 refresh token.
+// See "Using the Refresh Token" section in:
+// http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
+//
+// This class should be used on a single thread, but it can be whichever thread
+// that you like.
+// Also, do not reuse the same instance. Once Start() is called, the instance
+// should not be reused.
+//
+// Usage:
+// * Create an instance with a consumer.
+// * Call Start()
+// * The consumer passed in the constructor will be called on the same
+// thread Start was called with the results.
+//
+// This class can handle one request at a time. To parallelize requests,
+// create multiple instances.
+class OAuth2AccessTokenFetcherImpl : public OAuth2AccessTokenFetcher,
+ public net::URLFetcherDelegate {
+ public:
+ OAuth2AccessTokenFetcherImpl(OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter,
+ const std::string& refresh_token);
+ virtual ~OAuth2AccessTokenFetcherImpl();
+
+ // Implementation of OAuth2AccessTokenFetcher
+ virtual void Start(const std::string& client_id,
+ const std::string& client_secret,
+ const std::vector<std::string>& scopes) OVERRIDE;
+
+ virtual void CancelRequest() OVERRIDE;
+
+ // Implementation of net::URLFetcherDelegate
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ enum State {
+ INITIAL,
+ GET_ACCESS_TOKEN_STARTED,
+ GET_ACCESS_TOKEN_DONE,
+ ERROR_STATE,
+ };
+
+ // Helper methods for the flow.
+ void StartGetAccessToken();
+ void EndGetAccessToken(const net::URLFetcher* source);
+
+ // Helper mehtods for reporting back results.
+ void OnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time);
+ void OnGetTokenFailure(const GoogleServiceAuthError& error);
+
+ // Other helpers.
+ static GURL MakeGetAccessTokenUrl();
+ static std::string MakeGetAccessTokenBody(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes);
+
+ static bool ParseGetAccessTokenSuccessResponse(const net::URLFetcher* source,
+ std::string* access_token,
+ int* expires_in);
+
+ static bool ParseGetAccessTokenFailureResponse(const net::URLFetcher* source,
+ std::string* error);
+
+ // State that is set during construction.
+ net::URLRequestContextGetter* const getter_;
+ std::string refresh_token_;
+ State state_;
+
+ // While a fetch is in progress.
+ scoped_ptr<net::URLFetcher> fetcher_;
+ std::string client_id_;
+ std::string client_secret_;
+ std::vector<std::string> scopes_;
+
+ friend class OAuth2AccessTokenFetcherImplTest;
+ FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherImplTest,
+ ParseGetAccessTokenResponse);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherImplTest,
+ MakeGetAccessTokenBody);
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2AccessTokenFetcherImpl);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_IMPL_H_
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
index 135e292d7c0..f263932d4cf 100644
--- a/chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
@@ -1,17 +1,17 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2014 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.
//
-// A complete set of unit tests for OAuth2AccessTokenFetcher.
+// A complete set of unit tests for OAuth2AccessTokenFetcherImpl.
#include <string>
#include "base/memory/scoped_ptr.h"
-#include "content/public/test/test_browser_thread_bundle.h"
+#include "base/run_loop.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher.h"
@@ -57,18 +57,15 @@ static const char kValidFailureTokenResponse[] =
class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
public URLFetcherFactory {
-public:
- MockUrlFetcherFactory()
- : ScopedURLFetcherFactory(this) {
- }
+ public:
+ MockUrlFetcherFactory() : ScopedURLFetcherFactory(this) {}
virtual ~MockUrlFetcherFactory() {}
- MOCK_METHOD4(
- CreateURLFetcher,
- URLFetcher* (int id,
- const GURL& url,
- URLFetcher::RequestType request_type,
- URLFetcherDelegate* d));
+ MOCK_METHOD4(CreateURLFetcher,
+ URLFetcher*(int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d));
};
class MockOAuth2AccessTokenConsumer : public OAuth2AccessTokenConsumer {
@@ -76,26 +73,28 @@ class MockOAuth2AccessTokenConsumer : public OAuth2AccessTokenConsumer {
MockOAuth2AccessTokenConsumer() {}
~MockOAuth2AccessTokenConsumer() {}
- MOCK_METHOD2(OnGetTokenSuccess, void(const std::string& access_token,
- const base::Time& expiration_time));
- MOCK_METHOD1(OnGetTokenFailure,
- void(const GoogleServiceAuthError& error));
+ MOCK_METHOD2(OnGetTokenSuccess,
+ void(const std::string& access_token,
+ const base::Time& expiration_time));
+ MOCK_METHOD1(OnGetTokenFailure, void(const GoogleServiceAuthError& error));
};
} // namespace
-class OAuth2AccessTokenFetcherTest : public testing::Test {
+class OAuth2AccessTokenFetcherImplTest : public testing::Test {
public:
- OAuth2AccessTokenFetcherTest()
- : request_context_getter_(new net::TestURLRequestContextGetter(
- base::MessageLoopProxy::current())),
- fetcher_(&consumer_, request_context_getter_) {
+ OAuth2AccessTokenFetcherImplTest()
+ : request_context_getter_(new net::TestURLRequestContextGetter(
+ base::MessageLoopProxy::current())),
+ fetcher_(&consumer_, request_context_getter_, "refresh_token") {
+ base::RunLoop().RunUntilIdle();
}
- virtual ~OAuth2AccessTokenFetcherTest() {}
+ virtual ~OAuth2AccessTokenFetcherImplTest() {}
- virtual TestURLFetcher* SetupGetAccessToken(
- bool fetch_succeeds, int response_code, const std::string& body) {
+ virtual TestURLFetcher* SetupGetAccessToken(bool fetch_succeeds,
+ int response_code,
+ const std::string& body) {
GURL url(GaiaUrls::GetInstance()->oauth2_token_url());
TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, &fetcher_);
URLRequestStatus::Status status =
@@ -114,47 +113,49 @@ class OAuth2AccessTokenFetcherTest : public testing::Test {
}
protected:
- content::TestBrowserThreadBundle thread_bundle_;
+ base::MessageLoop message_loop_;
MockUrlFetcherFactory factory_;
MockOAuth2AccessTokenConsumer consumer_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
- OAuth2AccessTokenFetcher fetcher_;
+ OAuth2AccessTokenFetcherImpl fetcher_;
};
// These four tests time out, see http://crbug.com/113446.
-TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_GetAccessTokenRequestFailure) {
+TEST_F(OAuth2AccessTokenFetcherImplTest,
+ DISABLED_GetAccessTokenRequestFailure) {
TestURLFetcher* url_fetcher = SetupGetAccessToken(false, 0, std::string());
EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
- fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.Start("client_id", "client_secret", ScopeList());
fetcher_.OnURLFetchComplete(url_fetcher);
}
-TEST_F(OAuth2AccessTokenFetcherTest,
+TEST_F(OAuth2AccessTokenFetcherImplTest,
DISABLED_GetAccessTokenResponseCodeFailure) {
TestURLFetcher* url_fetcher =
SetupGetAccessToken(true, net::HTTP_FORBIDDEN, std::string());
EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
- fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.Start("client_id", "client_secret", ScopeList());
fetcher_.OnURLFetchComplete(url_fetcher);
}
-TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_Success) {
- TestURLFetcher* url_fetcher = SetupGetAccessToken(
- true, net::HTTP_OK, kValidTokenResponse);
+TEST_F(OAuth2AccessTokenFetcherImplTest, DISABLED_Success) {
+ TestURLFetcher* url_fetcher =
+ SetupGetAccessToken(true, net::HTTP_OK, kValidTokenResponse);
EXPECT_CALL(consumer_, OnGetTokenSuccess("at1", _)).Times(1);
- fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.Start("client_id", "client_secret", ScopeList());
fetcher_.OnURLFetchComplete(url_fetcher);
}
-TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_MakeGetAccessTokenBody) {
+TEST_F(OAuth2AccessTokenFetcherImplTest, DISABLED_MakeGetAccessTokenBody) {
{ // No scope.
std::string body =
"client_id=cid1&"
"client_secret=cs1&"
"grant_type=refresh_token&"
"refresh_token=rt1";
- EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
- "cid1", "cs1", "rt1", ScopeList()));
+ EXPECT_EQ(body,
+ OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", ScopeList()));
}
{ // One scope.
@@ -166,8 +167,9 @@ TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_MakeGetAccessTokenBody) {
"scope=https://www.googleapis.com/foo";
ScopeList scopes;
scopes.push_back("https://www.googleapis.com/foo");
- EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
- "cid1", "cs1", "rt1", scopes));
+ EXPECT_EQ(body,
+ OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", scopes));
}
{ // Multiple scopes.
@@ -183,8 +185,9 @@ TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_MakeGetAccessTokenBody) {
scopes.push_back("https://www.googleapis.com/foo");
scopes.push_back("https://www.googleapis.com/bar");
scopes.push_back("https://www.googleapis.com/baz");
- EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
- "cid1", "cs1", "rt1", scopes));
+ EXPECT_EQ(body,
+ OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", scopes));
}
}
@@ -193,15 +196,16 @@ TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_MakeGetAccessTokenBody) {
#define MAYBE_ParseGetAccessTokenResponse DISABLED_ParseGetAccessTokenResponse
#else
#define MAYBE_ParseGetAccessTokenResponse ParseGetAccessTokenResponse
-#endif // defined(OS_WIN)
-TEST_F(OAuth2AccessTokenFetcherTest, MAYBE_ParseGetAccessTokenResponse) {
+#endif // defined(OS_WIN)
+TEST_F(OAuth2AccessTokenFetcherImplTest, MAYBE_ParseGetAccessTokenResponse) {
{ // No body.
TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
std::string at;
int expires_in;
- EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse(
- &url_fetcher, &at, &expires_in));
+ EXPECT_FALSE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
+ &url_fetcher, &at, &expires_in));
EXPECT_TRUE(at.empty());
}
{ // Bad json.
@@ -210,8 +214,9 @@ TEST_F(OAuth2AccessTokenFetcherTest, MAYBE_ParseGetAccessTokenResponse) {
std::string at;
int expires_in;
- EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse(
- &url_fetcher, &at, &expires_in));
+ EXPECT_FALSE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
+ &url_fetcher, &at, &expires_in));
EXPECT_TRUE(at.empty());
}
{ // Valid json: access token missing.
@@ -220,8 +225,9 @@ TEST_F(OAuth2AccessTokenFetcherTest, MAYBE_ParseGetAccessTokenResponse) {
std::string at;
int expires_in;
- EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse(
- &url_fetcher, &at, &expires_in));
+ EXPECT_FALSE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
+ &url_fetcher, &at, &expires_in));
EXPECT_TRUE(at.empty());
}
{ // Valid json: all good.
@@ -230,27 +236,30 @@ TEST_F(OAuth2AccessTokenFetcherTest, MAYBE_ParseGetAccessTokenResponse) {
std::string at;
int expires_in;
- EXPECT_TRUE(OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse(
- &url_fetcher, &at, &expires_in));
+ EXPECT_TRUE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
+ &url_fetcher, &at, &expires_in));
EXPECT_EQ("at1", at);
EXPECT_EQ(3600, expires_in);
}
{ // Valid json: invalid error response.
- TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
- url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
-
- std::string error;
- EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenFailureResponse(
- &url_fetcher, &error));
- EXPECT_TRUE(error.empty());
- }
- { // Valid json: error response.
- TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
- url_fetcher.SetResponseString(kValidFailureTokenResponse);
-
- std::string error;
- EXPECT_TRUE(OAuth2AccessTokenFetcher::ParseGetAccessTokenFailureResponse(
- &url_fetcher, &error));
- EXPECT_EQ("invalid_grant", error);
- }
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
+
+ std::string error;
+ EXPECT_FALSE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
+ &url_fetcher, &error));
+ EXPECT_TRUE(error.empty());
+ }
+ { // Valid json: error response.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kValidFailureTokenResponse);
+
+ std::string error;
+ EXPECT_TRUE(
+ OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
+ &url_fetcher, &error));
+ EXPECT_EQ("invalid_grant", error);
+ }
}
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow.cc b/chromium/google_apis/gaia/oauth2_api_call_flow.cc
index 4c4940d9853..08312ebe5d3 100644
--- a/chromium/google_apis/gaia/oauth2_api_call_flow.cc
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow.cc
@@ -10,6 +10,7 @@
#include "base/basictypes.h"
#include "base/strings/stringprintf.h"
#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
@@ -108,7 +109,6 @@ void OAuth2ApiCallFlow::BeginMintAccessToken() {
oauth2_access_token_fetcher_->Start(
GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
- refresh_token_,
scopes_);
}
@@ -126,7 +126,7 @@ void OAuth2ApiCallFlow::EndMintAccessToken(
}
OAuth2AccessTokenFetcher* OAuth2ApiCallFlow::CreateAccessTokenFetcher() {
- return new OAuth2AccessTokenFetcher(this, context_);
+ return new OAuth2AccessTokenFetcherImpl(this, context_, refresh_token_);
}
void OAuth2ApiCallFlow::OnURLFetchComplete(const net::URLFetcher* source) {
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow.h b/chromium/google_apis/gaia/oauth2_api_call_flow.h
index fccd1e753be..b29bc8a1390 100644
--- a/chromium/google_apis/gaia/oauth2_api_call_flow.h
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow.h
@@ -7,10 +7,12 @@
#include <string>
+#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
class GoogleServiceAuthError;
class OAuth2MintTokenFlowTest;
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc b/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc
index d56a613eded..754b15e95fb 100644
--- a/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc
@@ -13,7 +13,7 @@
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "google_apis/gaia/oauth2_api_call_flow.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
@@ -70,18 +70,18 @@ class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
URLFetcherDelegate* d));
};
-class MockAccessTokenFetcher : public OAuth2AccessTokenFetcher {
+class MockAccessTokenFetcher : public OAuth2AccessTokenFetcherImpl {
public:
MockAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
- net::URLRequestContextGetter* getter)
- : OAuth2AccessTokenFetcher(consumer, getter) {}
+ net::URLRequestContextGetter* getter,
+ const std::string& refresh_token)
+ : OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token) {}
~MockAccessTokenFetcher() {}
- MOCK_METHOD4(Start,
- void (const std::string& client_id,
- const std::string& client_secret,
- const std::string& refresh_token,
- const std::vector<std::string>& scopes));
+ MOCK_METHOD3(Start,
+ void(const std::string& client_id,
+ const std::string& client_secret,
+ const std::vector<std::string>& scopes));
};
class MockApiCallFlow : public OAuth2ApiCallFlow {
@@ -110,13 +110,11 @@ class MockApiCallFlow : public OAuth2ApiCallFlow {
class OAuth2ApiCallFlowTest : public testing::Test {
protected:
- void SetupAccessTokenFetcher(
- const std::string& rt, const std::vector<std::string>& scopes) {
+ void SetupAccessTokenFetcher(const std::vector<std::string>& scopes) {
EXPECT_CALL(*access_token_fetcher_,
- Start(GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
- GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
- rt, scopes))
- .Times(1);
+ Start(GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ scopes)).Times(1);
EXPECT_CALL(*flow_, CreateAccessTokenFetcher())
.WillOnce(Return(access_token_fetcher_.release()));
}
@@ -146,8 +144,8 @@ class OAuth2ApiCallFlowTest : public testing::Test {
message_loop_.message_loop_proxy());
flow_.reset(new MockApiCallFlow(
request_context_getter, refresh_token, access_token, scopes));
- access_token_fetcher_.reset(
- new MockAccessTokenFetcher(flow_.get(), request_context_getter));
+ access_token_fetcher_.reset(new MockAccessTokenFetcher(
+ flow_.get(), request_context_getter, refresh_token));
}
TestURLFetcher* SetupApiCall(bool succeeds, net::HttpStatusCode status) {
@@ -188,7 +186,7 @@ TEST_F(OAuth2ApiCallFlowTest, SecondApiCallSucceeds) {
CreateFlow(rt, at, scopes);
TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
flow_->Start();
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
flow_->OnURLFetchComplete(url_fetcher1);
TestURLFetcher* url_fetcher2 = SetupApiCall(true, net::HTTP_OK);
EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher2));
@@ -206,7 +204,7 @@ TEST_F(OAuth2ApiCallFlowTest, SecondApiCallFails) {
CreateFlow(rt, at, scopes);
TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
flow_->Start();
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
flow_->OnURLFetchComplete(url_fetcher1);
TestURLFetcher* url_fetcher2 = SetupApiCall(false, net::HTTP_UNAUTHORIZED);
EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher2));
@@ -224,7 +222,7 @@ TEST_F(OAuth2ApiCallFlowTest, NewTokenGenerationFails) {
CreateFlow(rt, at, scopes);
TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
flow_->Start();
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
flow_->OnURLFetchComplete(url_fetcher);
GoogleServiceAuthError error(
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
@@ -238,7 +236,7 @@ TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenFirstApiCallSucceeds) {
std::vector<std::string> scopes(CreateTestScopes());
CreateFlow(rt, std::string(), scopes);
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_OK);
EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher));
flow_->Start();
@@ -254,7 +252,7 @@ TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenApiCallFails) {
std::vector<std::string> scopes(CreateTestScopes());
CreateFlow(rt, std::string(), scopes);
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
TestURLFetcher* url_fetcher = SetupApiCall(false, net::HTTP_BAD_GATEWAY);
EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher));
flow_->Start();
@@ -270,7 +268,7 @@ TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenNewTokenGenerationFails) {
std::vector<std::string> scopes(CreateTestScopes());
CreateFlow(rt, std::string(), scopes);
- SetupAccessTokenFetcher(rt, scopes);
+ SetupAccessTokenFetcher(scopes);
GoogleServiceAuthError error(
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
EXPECT_CALL(*flow_, ProcessMintAccessTokenFailure(error));
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow.cc b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc
index a1e3ff3cfeb..2318b50576f 100644
--- a/chromium/google_apis/gaia/oauth2_mint_token_flow.cc
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc
@@ -65,15 +65,15 @@ static GoogleServiceAuthError CreateAuthError(const net::URLFetcher* source) {
std::string response_body;
source->GetResponseAsString(&response_body);
- scoped_ptr<Value> value(base::JSONReader::Read(response_body));
- DictionaryValue* response;
+ scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
+ base::DictionaryValue* response;
if (!value.get() || !value->GetAsDictionary(&response)) {
return GoogleServiceAuthError::FromUnexpectedServiceResponse(
base::StringPrintf(
"Not able to parse a JSON object from a service response. "
"HTTP Status of the response is: %d", source->GetResponseCode()));
}
- DictionaryValue* error;
+ base::DictionaryValue* error;
if (!response->GetDictionary(kError, &error)) {
return GoogleServiceAuthError::FromUnexpectedServiceResponse(
"Not able to find a detailed error in a service response.");
@@ -266,12 +266,12 @@ bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
break;
}
- TrimWhitespace(entry.description, TRIM_ALL, &entry.description);
+ base::TrimWhitespace(entry.description, base::TRIM_ALL, &entry.description);
static const base::string16 detail_separators =
- ASCIIToUTF16(kDetailSeparators);
+ base::ASCIIToUTF16(kDetailSeparators);
Tokenize(detail, detail_separators, &entry.details);
for (size_t i = 0; i < entry.details.size(); i++)
- TrimWhitespace(entry.details[i], TRIM_ALL, &entry.details[i]);
+ base::TrimWhitespace(entry.details[i], base::TRIM_ALL, &entry.details[i]);
issue_advice->push_back(entry);
}
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow.h b/chromium/google_apis/gaia/oauth2_mint_token_flow.h
index 06c823ce4bc..6c53c945648 100644
--- a/chromium/google_apis/gaia/oauth2_mint_token_flow.h
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow.h
@@ -8,9 +8,11 @@
#include <string>
#include <vector>
+#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "google_apis/gaia/oauth2_api_call_flow.h"
+#include "url/gurl.h"
class GoogleServiceAuthError;
class OAuth2MintTokenFlowTest;
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc b/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
index 7cd3d672c62..8fd359f8d50 100644
--- a/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
@@ -111,13 +111,13 @@ std::vector<std::string> CreateTestScopes() {
static IssueAdviceInfo CreateIssueAdvice() {
IssueAdviceInfo ia;
IssueAdviceInfoEntry e1;
- e1.description = ASCIIToUTF16("Manage your calendars");
- e1.details.push_back(ASCIIToUTF16("View and manage your calendars"));
+ e1.description = base::ASCIIToUTF16("Manage your calendars");
+ e1.details.push_back(base::ASCIIToUTF16("View and manage your calendars"));
ia.push_back(e1);
IssueAdviceInfoEntry e2;
- e2.description = ASCIIToUTF16("Manage your documents");
- e2.details.push_back(ASCIIToUTF16("View your documents"));
- e2.details.push_back(ASCIIToUTF16("Upload new documents"));
+ e2.description = base::ASCIIToUTF16("Manage your documents");
+ e2.details.push_back(base::ASCIIToUTF16("View your documents"));
+ e2.details.push_back(base::ASCIIToUTF16("Upload new documents"));
ia.push_back(e2);
return ia;
}
@@ -170,9 +170,9 @@ class OAuth2MintTokenFlowTest : public testing::Test {
// Helper to parse the given string to DictionaryValue.
static base::DictionaryValue* ParseJson(const std::string& str) {
- scoped_ptr<Value> value(base::JSONReader::Read(str));
+ scoped_ptr<base::Value> value(base::JSONReader::Read(str));
EXPECT_TRUE(value.get());
- EXPECT_EQ(Value::TYPE_DICTIONARY, value->GetType());
+ EXPECT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
return static_cast<base::DictionaryValue*>(value.release());
}
diff --git a/chromium/google_apis/gaia/oauth2_token_service.cc b/chromium/google_apis/gaia/oauth2_token_service.cc
index 9684281097b..7a183b6e6c5 100644
--- a/chromium/google_apis/gaia/oauth2_token_service.cc
+++ b/chromium/google_apis/gaia/oauth2_token_service.cc
@@ -15,6 +15,7 @@
#include "base/timer/timer.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "net/url_request/url_request_context_getter.h"
int OAuth2TokenService::max_fetch_retry_num_ = 5;
@@ -61,6 +62,10 @@ std::string OAuth2TokenService::RequestImpl::GetAccountId() const {
return account_id_;
}
+std::string OAuth2TokenService::RequestImpl::GetConsumerId() const {
+ return consumer_->id();
+}
+
void OAuth2TokenService::RequestImpl::InformConsumer(
const GoogleServiceAuthError& error,
const std::string& access_token,
@@ -72,10 +77,8 @@ void OAuth2TokenService::RequestImpl::InformConsumer(
consumer_->OnGetTokenFailure(this, error);
}
-// Class that fetches an OAuth2 access token for a given set of scopes and
-// OAuth2 refresh token.
-
-// Class that fetches OAuth2 access tokens for given scopes and refresh token.
+// Class that fetches an OAuth2 access token for a given account id and set of
+// scopes.
//
// It aims to meet OAuth2TokenService's requirements on token fetching. Retry
// mechanism is used to handle failures.
@@ -105,14 +108,13 @@ void OAuth2TokenService::RequestImpl::InformConsumer(
class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer {
public:
// Creates a Fetcher and starts fetching an OAuth2 access token for
- // |refresh_token| and |scopes| in the request context obtained by |getter|.
+ // |account_id| and |scopes| in the request context obtained by |getter|.
// The given |oauth2_token_service| will be informed when fetching is done.
static Fetcher* CreateAndStart(OAuth2TokenService* oauth2_token_service,
const std::string& account_id,
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
- const std::string& refresh_token,
const ScopeSet& scopes,
base::WeakPtr<RequestImpl> waiting_request);
virtual ~Fetcher();
@@ -123,10 +125,13 @@ class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer {
// Returns count of waiting requests.
size_t GetWaitingRequestCount() const;
+ const std::vector<base::WeakPtr<RequestImpl> >& waiting_requests() const {
+ return waiting_requests_;
+ }
+
void Cancel();
const ScopeSet& GetScopeSet() const;
- const std::string& GetRefreshToken() const;
const std::string& GetClientId() const;
const std::string& GetAccountId() const;
@@ -146,7 +151,6 @@ class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer {
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
- const std::string& refresh_token,
const OAuth2TokenService::ScopeSet& scopes,
base::WeakPtr<RequestImpl> waiting_request);
void Start();
@@ -162,7 +166,6 @@ class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer {
OAuth2TokenService* const oauth2_token_service_;
scoped_refptr<net::URLRequestContextGetter> getter_;
const std::string account_id_;
- const std::string refresh_token_;
const ScopeSet scopes_;
std::vector<base::WeakPtr<RequestImpl> > waiting_requests_;
@@ -191,7 +194,6 @@ OAuth2TokenService::Fetcher* OAuth2TokenService::Fetcher::CreateAndStart(
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
- const std::string& refresh_token,
const OAuth2TokenService::ScopeSet& scopes,
base::WeakPtr<RequestImpl> waiting_request) {
OAuth2TokenService::Fetcher* fetcher = new Fetcher(
@@ -200,7 +202,6 @@ OAuth2TokenService::Fetcher* OAuth2TokenService::Fetcher::CreateAndStart(
getter,
client_id,
client_secret,
- refresh_token,
scopes,
waiting_request);
fetcher->Start();
@@ -213,13 +214,11 @@ OAuth2TokenService::Fetcher::Fetcher(
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
- const std::string& refresh_token,
const OAuth2TokenService::ScopeSet& scopes,
base::WeakPtr<RequestImpl> waiting_request)
: oauth2_token_service_(oauth2_token_service),
getter_(getter),
account_id_(account_id),
- refresh_token_(refresh_token),
scopes_(scopes),
retry_number_(0),
error_(GoogleServiceAuthError::SERVICE_UNAVAILABLE),
@@ -227,7 +226,6 @@ OAuth2TokenService::Fetcher::Fetcher(
client_secret_(client_secret) {
DCHECK(oauth2_token_service_);
DCHECK(getter_.get());
- DCHECK(refresh_token_.length());
waiting_requests_.push_back(waiting_request);
}
@@ -238,10 +236,11 @@ OAuth2TokenService::Fetcher::~Fetcher() {
}
void OAuth2TokenService::Fetcher::Start() {
- fetcher_.reset(new OAuth2AccessTokenFetcher(this, getter_.get()));
+ fetcher_.reset(oauth2_token_service_->CreateAccessTokenFetcher(
+ account_id_, getter_.get(), this));
+ DCHECK(fetcher_);
fetcher_->Start(client_id_,
client_secret_,
- refresh_token_,
std::vector<std::string>(scopes_.begin(), scopes_.end()));
retry_timer_.Stop();
}
@@ -273,11 +272,12 @@ void OAuth2TokenService::Fetcher::OnGetTokenFailure(
fetcher_.reset();
if (ShouldRetry(error) && retry_number_ < max_fetch_retry_num_) {
- int64 backoff = ComputeExponentialBackOffMilliseconds(retry_number_);
+ base::TimeDelta backoff = base::TimeDelta::FromMilliseconds(
+ ComputeExponentialBackOffMilliseconds(retry_number_));
++retry_number_;
retry_timer_.Stop();
retry_timer_.Start(FROM_HERE,
- base::TimeDelta::FromMilliseconds(backoff),
+ backoff,
this,
&OAuth2TokenService::Fetcher::Start);
return;
@@ -346,10 +346,6 @@ const OAuth2TokenService::ScopeSet& OAuth2TokenService::Fetcher::GetScopeSet()
return scopes_;
}
-const std::string& OAuth2TokenService::Fetcher::GetRefreshToken() const {
- return refresh_token_;
-}
-
const std::string& OAuth2TokenService::Fetcher::GetClientId() const {
return client_id_;
}
@@ -364,8 +360,8 @@ OAuth2TokenService::Request::Request() {
OAuth2TokenService::Request::~Request() {
}
-OAuth2TokenService::Consumer::Consumer() {
-}
+OAuth2TokenService::Consumer::Consumer(const std::string& id)
+ : id_(id) {}
OAuth2TokenService::Consumer::~Consumer() {
}
@@ -387,10 +383,13 @@ void OAuth2TokenService::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
-bool OAuth2TokenService::RefreshTokenIsAvailable(
- const std::string& account_id) {
- DCHECK(CalledOnValidThread());
- return !GetRefreshToken(account_id).empty();
+void OAuth2TokenService::AddDiagnosticsObserver(DiagnosticsObserver* observer) {
+ diagnostics_observer_list_.AddObserver(observer);
+}
+
+void OAuth2TokenService::RemoveDiagnosticsObserver(
+ DiagnosticsObserver* observer) {
+ diagnostics_observer_list_.RemoveObserver(observer);
}
std::vector<std::string> OAuth2TokenService::GetAccounts() {
@@ -451,13 +450,24 @@ OAuth2TokenService::StartRequestForClientWithContext(
Consumer* consumer) {
DCHECK(CalledOnValidThread());
- scoped_ptr<RequestImpl> request = CreateRequest(account_id, consumer);
+ scoped_ptr<RequestImpl> request(new RequestImpl(account_id, consumer));
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnAccessTokenRequested(account_id,
+ consumer->id(),
+ scopes));
if (!RefreshTokenIsAvailable(account_id)) {
+ GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
+
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnFetchAccessTokenComplete(
+ account_id, consumer->id(), scopes, error,
+ base::Time()));
+
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&RequestImpl::InformConsumer,
request->AsWeakPtr(),
- GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP),
+ error,
std::string(),
base::Time()));
return request.PassAs<Request>();
@@ -479,20 +489,12 @@ OAuth2TokenService::StartRequestForClientWithContext(
return request.PassAs<Request>();
}
-scoped_ptr<OAuth2TokenService::RequestImpl> OAuth2TokenService::CreateRequest(
- const std::string& account_id,
- Consumer* consumer) {
- return scoped_ptr<RequestImpl>(new RequestImpl(account_id, consumer));
-}
-
void OAuth2TokenService::FetchOAuth2Token(RequestImpl* request,
const std::string& account_id,
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
const ScopeSet& scopes) {
- std::string refresh_token = GetRefreshToken(account_id);
-
// If there is already a pending fetcher for |scopes| and |account_id|,
// simply register this |request| for those results rather than starting
// a new fetcher.
@@ -512,7 +514,6 @@ void OAuth2TokenService::FetchOAuth2Token(RequestImpl* request,
getter,
client_id,
client_secret,
- refresh_token,
scopes,
request->AsWeakPtr());
}
@@ -523,6 +524,13 @@ void OAuth2TokenService::StartCacheLookupRequest(
OAuth2TokenService::Consumer* consumer) {
CHECK(HasCacheEntry(request_parameters));
const CacheEntry* cache_entry = GetCacheEntry(request_parameters);
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnFetchAccessTokenComplete(
+ request_parameters.account_id,
+ consumer->id(),
+ request_parameters.scopes,
+ GoogleServiceAuthError::AuthErrorNone(),
+ cache_entry->expiration_date));
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&RequestImpl::InformConsumer,
request->AsWeakPtr(),
@@ -594,11 +602,26 @@ void OAuth2TokenService::OnFetchComplete(Fetcher* fetcher) {
// By (1), |fetcher| is created by this service.
// Then by (2), |fetcher| is recorded in |pending_fetchers_|.
// Then by (3), |fetcher_| is mapped to its refresh token and ScopeSet.
+ RequestParameters request_param(fetcher->GetClientId(),
+ fetcher->GetAccountId(),
+ fetcher->GetScopeSet());
+
+ const OAuth2TokenService::CacheEntry* entry = GetCacheEntry(request_param);
+ const std::vector<base::WeakPtr<RequestImpl> >& requests =
+ fetcher->waiting_requests();
+ for (size_t i = 0; i < requests.size(); ++i) {
+ const RequestImpl* req = requests[i].get();
+ if (req) {
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnFetchAccessTokenComplete(
+ req->GetAccountId(), req->GetConsumerId(),
+ fetcher->GetScopeSet(), fetcher->error(),
+ entry ? entry->expiration_date : base::Time()));
+ }
+ }
+
std::map<RequestParameters, Fetcher*>::iterator iter =
- pending_fetchers_.find(RequestParameters(
- fetcher->GetClientId(),
- fetcher->GetAccountId(),
- fetcher->GetScopeSet()));
+ pending_fetchers_.find(request_param);
DCHECK(iter != pending_fetchers_.end());
DCHECK_EQ(fetcher, iter->second);
pending_fetchers_.erase(iter);
@@ -630,6 +653,9 @@ bool OAuth2TokenService::RemoveCacheEntry(
TokenCache::iterator token_iterator = token_cache_.find(request_parameters);
if (token_iterator != token_cache_.end() &&
token_iterator->second.access_token == token_to_remove) {
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnTokenRemoved(request_parameters.account_id,
+ request_parameters.scopes));
token_cache_.erase(token_iterator);
return true;
}
@@ -659,6 +685,13 @@ void OAuth2TokenService::UpdateAuthError(
void OAuth2TokenService::ClearCache() {
DCHECK(CalledOnValidThread());
+ for (TokenCache::iterator iter = token_cache_.begin();
+ iter != token_cache_.end(); ++iter) {
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnTokenRemoved(iter->first.account_id,
+ iter->first.scopes));
+ }
+
token_cache_.clear();
}
@@ -668,6 +701,8 @@ void OAuth2TokenService::ClearCacheForAccount(const std::string& account_id) {
iter != token_cache_.end();
/* iter incremented in body */) {
if (iter->first.account_id == account_id) {
+ FOR_EACH_OBSERVER(DiagnosticsObserver, diagnostics_observer_list_,
+ OnTokenRemoved(account_id, iter->first.scopes));
token_cache_.erase(iter++);
} else {
++iter;
diff --git a/chromium/google_apis/gaia/oauth2_token_service.h b/chromium/google_apis/gaia/oauth2_token_service.h
index 1745315abf4..685b9dbb340 100644
--- a/chromium/google_apis/gaia/oauth2_token_service.h
+++ b/chromium/google_apis/gaia/oauth2_token_service.h
@@ -26,6 +26,7 @@ class URLRequestContextGetter;
}
class GoogleServiceAuthError;
+class OAuth2AccessTokenFetcher;
// Abstract base class for a service that fetches and caches OAuth2 access
// tokens. Concrete subclasses should implement GetRefreshToken to return
@@ -52,6 +53,9 @@ class GoogleServiceAuthError;
// delete the request even once the callback has been invoked.
class OAuth2TokenService : public base::NonThreadSafe {
public:
+ // A set of scopes in OAuth2 authentication.
+ typedef std::set<std::string> ScopeSet;
+
// Class representing a request that fetches an OAuth2 access token.
class Request {
public:
@@ -65,8 +69,11 @@ class OAuth2TokenService : public base::NonThreadSafe {
// which will be called back when the request completes.
class Consumer {
public:
- Consumer();
+ Consumer(const std::string& id);
virtual ~Consumer();
+
+ std::string id() const { return id_; }
+
// |request| is a Request that is started by this consumer and has
// completed.
virtual void OnGetTokenSuccess(const Request* request,
@@ -74,10 +81,12 @@ class OAuth2TokenService : public base::NonThreadSafe {
const base::Time& expiration_time) = 0;
virtual void OnGetTokenFailure(const Request* request,
const GoogleServiceAuthError& error) = 0;
+ private:
+ std::string id_;
};
- // Classes that want to listen for token availability should implement this
- // interface and register with the AddObserver() call.
+ // Classes that want to listen for refresh token availability should
+ // implement this interface and register with the AddObserver() call.
class Observer {
public:
// Called whenever a new login-scoped refresh token is available for
@@ -91,12 +100,31 @@ class OAuth2TokenService : public base::NonThreadSafe {
// Called after all refresh tokens are loaded during OAuth2TokenService
// startup.
virtual void OnRefreshTokensLoaded() {}
+
protected:
virtual ~Observer() {}
};
- // A set of scopes in OAuth2 authentication.
- typedef std::set<std::string> ScopeSet;
+ // Classes that want to monitor status of access token and access token
+ // request should implement this interface and register with the
+ // AddDiagnosticsObserver() call.
+ class DiagnosticsObserver {
+ public:
+ // Called when receiving request for access token.
+ virtual void OnAccessTokenRequested(const std::string& account_id,
+ const std::string& consumer_id,
+ const ScopeSet& scopes) = 0;
+ // Called when access token fetching finished successfully or
+ // unsuccessfully. |expiration_time| are only valid with
+ // successful completion.
+ virtual void OnFetchAccessTokenComplete(const std::string& account_id,
+ const std::string& consumer_id,
+ const ScopeSet& scopes,
+ GoogleServiceAuthError error,
+ base::Time expiration_time) = 0;
+ virtual void OnTokenRemoved(const std::string& account_id,
+ const ScopeSet& scopes) = 0;
+ };
OAuth2TokenService();
virtual ~OAuth2TokenService();
@@ -105,6 +133,10 @@ class OAuth2TokenService : public base::NonThreadSafe {
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
+ // Add or remove observers of this token service.
+ void AddDiagnosticsObserver(DiagnosticsObserver* observer);
+ void RemoveDiagnosticsObserver(DiagnosticsObserver* observer);
+
// Checks in the cache for a valid access token for a specified |account_id|
// and |scopes|, and if not found starts a request for an OAuth2 access token
// using the OAuth2 refresh token maintained by this instance for that
@@ -141,7 +173,7 @@ class OAuth2TokenService : public base::NonThreadSafe {
// Returns true if a refresh token exists for |account_id|. If false, calls to
// |StartRequest| will result in a Consumer::OnGetTokenFailure callback.
- virtual bool RefreshTokenIsAvailable(const std::string& account_id);
+ virtual bool RefreshTokenIsAvailable(const std::string& account_id) const = 0;
// Mark an OAuth2 |access_token| issued for |account_id| and |scopes| as
// invalid. This should be done if the token was received from this class,
@@ -184,6 +216,8 @@ class OAuth2TokenService : public base::NonThreadSafe {
// Overridden from Request:
virtual std::string GetAccountId() const OVERRIDE;
+ std::string GetConsumerId() const;
+
// Informs |consumer_| that this request is completed.
void InformConsumer(const GoogleServiceAuthError& error,
const std::string& access_token,
@@ -195,10 +229,6 @@ class OAuth2TokenService : public base::NonThreadSafe {
Consumer* const consumer_;
};
- // Subclasses should return the maintained refresh token for |account_id|.
- // If no token is available, return an empty string.
- virtual std::string GetRefreshToken(const std::string& account_id) = 0;
-
// Subclasses can override if they want to report errors to the user.
virtual void UpdateAuthError(
const std::string& account_id,
@@ -232,13 +262,6 @@ class OAuth2TokenService : public base::NonThreadSafe {
virtual void FireRefreshTokenRevoked(const std::string& account_id);
virtual void FireRefreshTokensLoaded();
- // Creates a request implementation. Can be overriden by derived classes to
- // provide additional control of token consumption. |consumer| will outlive
- // the created request.
- virtual scoped_ptr<RequestImpl> CreateRequest(
- const std::string& account_id,
- Consumer* consumer);
-
// Fetches an OAuth token for the specified client/scopes. Virtual so it can
// be overridden for tests and for platform-specific behavior on Android.
virtual void FetchOAuth2Token(RequestImpl* request,
@@ -248,6 +271,16 @@ class OAuth2TokenService : public base::NonThreadSafe {
const std::string& client_secret,
const ScopeSet& scopes);
+ // Creates an access token fetcher for the given account id.
+ //
+ // Subclasses should override to create an access token fetcher for the given
+ // |account_id|. This method is only called if subclasses use the default
+ // implementation of |FetchOAuth2Token|.
+ virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) = 0;
+
// Invalidates the |access_token| issued for |account_id|, |client_id| and
// |scopes|. Virtual so it can be overriden for tests and for platform-
// specifc behavior.
@@ -334,10 +367,13 @@ class OAuth2TokenService : public base::NonThreadSafe {
// token using these parameters.
PendingFetcherMap pending_fetchers_;
- // List of observers to notify when token availability changes.
+ // List of observers to notify when refresh token availability changes.
// Makes sure list is empty on destruction.
ObserverList<Observer, true> observer_list_;
+ // List of observers to notify when access token status changes.
+ ObserverList<DiagnosticsObserver, true> diagnostics_observer_list_;
+
// Maximum number of retries in fetching an OAuth2 access token.
static int max_fetch_retry_num_;
diff --git a/chromium/google_apis/gaia/oauth2_token_service_request.cc b/chromium/google_apis/gaia/oauth2_token_service_request.cc
new file mode 100644
index 00000000000..67201524177
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_token_service_request.cc
@@ -0,0 +1,360 @@
+// Copyright 2014 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 "google_apis/gaia/oauth2_token_service_request.h"
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+
+OAuth2TokenServiceRequest::TokenServiceProvider::TokenServiceProvider() {
+}
+
+OAuth2TokenServiceRequest::TokenServiceProvider::~TokenServiceProvider() {
+}
+
+// Core serves as the base class for OAuth2TokenService operations. Each
+// operation should be modeled as a derived type.
+//
+// Core is used like this:
+//
+// 1. Constructed on owner thread.
+//
+// 2. Start() is called on owner thread, which calls StartOnTokenServiceThread()
+// on token service thread.
+//
+// 3. Request is executed.
+//
+// 4. Stop() is called on owner thread, which calls StopOnTokenServiceThread()
+// on token service thread.
+//
+// 5. Core is destroyed on owner thread.
+class OAuth2TokenServiceRequest::Core
+ : public base::NonThreadSafe,
+ public base::RefCountedThreadSafe<OAuth2TokenServiceRequest::Core> {
+ public:
+ // Note the thread where an instance of Core is constructed is referred to as
+ // the "owner thread" here.
+ Core(OAuth2TokenServiceRequest* owner, TokenServiceProvider* provider);
+
+ // Starts the core. Must be called on the owner thread.
+ void Start();
+
+ // Stops the core. Must be called on the owner thread.
+ void Stop();
+
+ // Returns true if this object has been stopped. Must be called on the owner
+ // thread.
+ bool IsStopped() const;
+
+ protected:
+ // Core must be destroyed on the owner thread. If data members must be
+ // cleaned up or destroyed on the token service thread, do so in the
+ // StopOnTokenServiceThread method.
+ virtual ~Core();
+
+ // Called on the token service thread.
+ virtual void StartOnTokenServiceThread() = 0;
+
+ // Called on the token service thread.
+ virtual void StopOnTokenServiceThread() = 0;
+
+ base::SingleThreadTaskRunner* token_service_task_runner();
+ OAuth2TokenService* token_service();
+ OAuth2TokenServiceRequest* owner();
+
+ private:
+ friend class base::RefCountedThreadSafe<OAuth2TokenServiceRequest::Core>;
+
+ void DoNothing();
+
+ scoped_refptr<base::SingleThreadTaskRunner> token_service_task_runner_;
+ OAuth2TokenServiceRequest* owner_;
+ TokenServiceProvider* provider_;
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+OAuth2TokenServiceRequest::Core::Core(OAuth2TokenServiceRequest* owner,
+ TokenServiceProvider* provider)
+ : owner_(owner), provider_(provider) {
+ DCHECK(owner_);
+ DCHECK(provider_);
+ token_service_task_runner_ = provider_->GetTokenServiceTaskRunner();
+ DCHECK(token_service_task_runner_);
+}
+
+OAuth2TokenServiceRequest::Core::~Core() {
+}
+
+void OAuth2TokenServiceRequest::Core::Start() {
+ DCHECK(CalledOnValidThread());
+ token_service_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OAuth2TokenServiceRequest::Core::StartOnTokenServiceThread,
+ this));
+}
+
+void OAuth2TokenServiceRequest::Core::Stop() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsStopped());
+
+ // Detaches |owner_| from this instance so |owner_| will be called back only
+ // if |Stop()| has never been called.
+ owner_ = NULL;
+
+ // We are stopping and will likely be destroyed soon. Use a reply closure
+ // (DoNothing) to retain "this" and ensure we are destroyed in the owner
+ // thread, not the task runner thread. PostTaskAndReply guarantees that the
+ // reply closure will execute after StopOnTokenServiceThread has completed.
+ token_service_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&OAuth2TokenServiceRequest::Core::StopOnTokenServiceThread,
+ this),
+ base::Bind(&OAuth2TokenServiceRequest::Core::DoNothing, this));
+}
+
+bool OAuth2TokenServiceRequest::Core::IsStopped() const {
+ DCHECK(CalledOnValidThread());
+ return owner_ == NULL;
+}
+
+base::SingleThreadTaskRunner*
+OAuth2TokenServiceRequest::Core::token_service_task_runner() {
+ return token_service_task_runner_;
+}
+
+OAuth2TokenService* OAuth2TokenServiceRequest::Core::token_service() {
+ DCHECK(token_service_task_runner_->BelongsToCurrentThread());
+ return provider_->GetTokenService();
+}
+
+OAuth2TokenServiceRequest* OAuth2TokenServiceRequest::Core::owner() {
+ DCHECK(CalledOnValidThread());
+ return owner_;
+}
+
+void OAuth2TokenServiceRequest::Core::DoNothing() {
+ DCHECK(CalledOnValidThread());
+}
+
+namespace {
+
+// An implementation of Core for getting an access token.
+class RequestCore : public OAuth2TokenServiceRequest::Core,
+ public OAuth2TokenService::Consumer {
+ public:
+ RequestCore(OAuth2TokenServiceRequest* owner,
+ OAuth2TokenServiceRequest::TokenServiceProvider* provider,
+ OAuth2TokenService::Consumer* consumer,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes);
+
+ // OAuth2TokenService::Consumer. Must be called on the token service thread.
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<RequestCore>;
+
+ // Must be destroyed on the owner thread.
+ virtual ~RequestCore();
+
+ // Core implementation.
+ virtual void StartOnTokenServiceThread() OVERRIDE;
+ virtual void StopOnTokenServiceThread() OVERRIDE;
+
+ void InformOwnerOnGetTokenSuccess(std::string access_token,
+ base::Time expiration_time);
+ void InformOwnerOnGetTokenFailure(GoogleServiceAuthError error);
+
+ scoped_refptr<base::SingleThreadTaskRunner> owner_task_runner_;
+ OAuth2TokenService::Consumer* const consumer_;
+ std::string account_id_;
+ OAuth2TokenService::ScopeSet scopes_;
+
+ // OAuth2TokenService request for fetching OAuth2 access token; it should be
+ // created, reset and accessed only on the token service thread.
+ scoped_ptr<OAuth2TokenService::Request> request_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestCore);
+};
+
+RequestCore::RequestCore(
+ OAuth2TokenServiceRequest* owner,
+ OAuth2TokenServiceRequest::TokenServiceProvider* provider,
+ OAuth2TokenService::Consumer* consumer,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes)
+ : OAuth2TokenServiceRequest::Core(owner, provider),
+ OAuth2TokenService::Consumer("oauth2_token_service"),
+ owner_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ consumer_(consumer),
+ account_id_(account_id),
+ scopes_(scopes) {
+ DCHECK(consumer_);
+ DCHECK(!account_id_.empty());
+ DCHECK(!scopes_.empty());
+}
+
+RequestCore::~RequestCore() {
+}
+
+void RequestCore::StartOnTokenServiceThread() {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ request_ = token_service()->StartRequest(account_id_, scopes_, this).Pass();
+}
+
+void RequestCore::StopOnTokenServiceThread() {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ request_.reset();
+}
+
+void RequestCore::OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ DCHECK_EQ(request_.get(), request);
+ owner_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&RequestCore::InformOwnerOnGetTokenSuccess,
+ this,
+ access_token,
+ expiration_time));
+ request_.reset();
+}
+
+void RequestCore::OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ DCHECK_EQ(request_.get(), request);
+ owner_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&RequestCore::InformOwnerOnGetTokenFailure, this, error));
+ request_.reset();
+}
+
+void RequestCore::InformOwnerOnGetTokenSuccess(std::string access_token,
+ base::Time expiration_time) {
+ DCHECK(CalledOnValidThread());
+ if (!IsStopped()) {
+ consumer_->OnGetTokenSuccess(owner(), access_token, expiration_time);
+ }
+}
+
+void RequestCore::InformOwnerOnGetTokenFailure(GoogleServiceAuthError error) {
+ DCHECK(CalledOnValidThread());
+ if (!IsStopped()) {
+ consumer_->OnGetTokenFailure(owner(), error);
+ }
+}
+
+// An implementation of Core for invalidating an access token.
+class InvalidateCore : public OAuth2TokenServiceRequest::Core {
+ public:
+ InvalidateCore(OAuth2TokenServiceRequest* owner,
+ OAuth2TokenServiceRequest::TokenServiceProvider* provider,
+ const std::string& access_token,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes);
+
+ private:
+ friend class base::RefCountedThreadSafe<InvalidateCore>;
+
+ // Must be destroyed on the owner thread.
+ virtual ~InvalidateCore();
+
+ // Core implementation.
+ virtual void StartOnTokenServiceThread() OVERRIDE;
+ virtual void StopOnTokenServiceThread() OVERRIDE;
+
+ std::string access_token_;
+ std::string account_id_;
+ OAuth2TokenService::ScopeSet scopes_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvalidateCore);
+};
+
+InvalidateCore::InvalidateCore(
+ OAuth2TokenServiceRequest* owner,
+ OAuth2TokenServiceRequest::TokenServiceProvider* provider,
+ const std::string& access_token,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes)
+ : OAuth2TokenServiceRequest::Core(owner, provider),
+ access_token_(access_token),
+ account_id_(account_id),
+ scopes_(scopes) {
+ DCHECK(!access_token_.empty());
+ DCHECK(!account_id_.empty());
+ DCHECK(!scopes.empty());
+}
+
+InvalidateCore::~InvalidateCore() {
+}
+
+void InvalidateCore::StartOnTokenServiceThread() {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ token_service()->InvalidateToken(account_id_, scopes_, access_token_);
+}
+
+void InvalidateCore::StopOnTokenServiceThread() {
+ DCHECK(token_service_task_runner()->BelongsToCurrentThread());
+ // Nothing to do.
+}
+
+} // namespace
+
+// static
+scoped_ptr<OAuth2TokenServiceRequest> OAuth2TokenServiceRequest::CreateAndStart(
+ TokenServiceProvider* provider,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer) {
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ new OAuth2TokenServiceRequest(account_id));
+ scoped_refptr<Core> core(
+ new RequestCore(request.get(), provider, consumer, account_id, scopes));
+ request->StartWithCore(core);
+ return request.Pass();
+}
+
+// static
+void OAuth2TokenServiceRequest::InvalidateToken(
+ OAuth2TokenServiceRequest::TokenServiceProvider* provider,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ const std::string& access_token) {
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ new OAuth2TokenServiceRequest(account_id));
+ scoped_refptr<Core> core(new InvalidateCore(
+ request.get(), provider, access_token, account_id, scopes));
+ request->StartWithCore(core);
+}
+
+OAuth2TokenServiceRequest::~OAuth2TokenServiceRequest() {
+ core_->Stop();
+}
+
+std::string OAuth2TokenServiceRequest::GetAccountId() const {
+ return account_id_;
+}
+
+OAuth2TokenServiceRequest::OAuth2TokenServiceRequest(
+ const std::string& account_id)
+ : account_id_(account_id) {
+ DCHECK(!account_id_.empty());
+}
+
+void OAuth2TokenServiceRequest::StartWithCore(const scoped_refptr<Core>& core) {
+ DCHECK(core);
+ core_ = core;
+ core_->Start();
+}
diff --git a/chromium/google_apis/gaia/oauth2_token_service_request.h b/chromium/google_apis/gaia/oauth2_token_service_request.h
new file mode 100644
index 00000000000..972e5184b85
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_token_service_request.h
@@ -0,0 +1,96 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_REQUEST_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_REQUEST_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/non_thread_safe.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+// OAuth2TokenServiceRequest represents an asynchronous request to an
+// OAuth2TokenService that may live in another thread.
+//
+// An OAuth2TokenServiceRequest can be created and started from any thread.
+class OAuth2TokenServiceRequest : public OAuth2TokenService::Request,
+ public base::NonThreadSafe {
+ public:
+ class Core;
+
+ // Interface for providing an OAuth2TokenService.
+ class TokenServiceProvider {
+ public:
+ TokenServiceProvider();
+ virtual ~TokenServiceProvider();
+
+ // Returns the task runner on which the token service lives.
+ //
+ // This method may be called from any thread.
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetTokenServiceTaskRunner() = 0;
+
+ // Returns a pointer to a token service.
+ //
+ // Caller does not own the token service and must not delete it. The token
+ // service must outlive all instances of OAuth2TokenServiceRequest.
+ //
+ // This method may only be called from the task runner returned by
+ // |GetTokenServiceTaskRunner|.
+ virtual OAuth2TokenService* GetTokenService() = 0;
+ };
+
+ // Creates and starts an access token request for |account_id| and |scopes|.
+ //
+ // |provider| is used to get the OAuth2TokenService and must outlive the
+ // returned request object.
+ //
+ // |account_id| must not be empty.
+ //
+ // |scopes| must not be empty.
+ //
+ // |consumer| will be invoked in the same thread that invoked CreateAndStart
+ // and must outlive the returned request object. Destroying the request
+ // object ensure that |consumer| will not be called. However, the actual
+ // network activities may not be canceled and the cache in OAuth2TokenService
+ // may be populated with the fetched results.
+ static scoped_ptr<OAuth2TokenServiceRequest> CreateAndStart(
+ TokenServiceProvider* provider,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer);
+
+ // Invalidates |access_token| for |account_id| and |scopes|.
+ //
+ // |provider| is used to get the OAuth2TokenService and must outlive the
+ // returned request object.
+ //
+ // |account_id| must not be empty.
+ //
+ // |scopes| must not be empty.
+ static void InvalidateToken(TokenServiceProvider* provider,
+ const std::string& account_id,
+ const OAuth2TokenService::ScopeSet& scopes,
+ const std::string& access_token);
+
+ virtual ~OAuth2TokenServiceRequest();
+
+ // OAuth2TokenService::Request.
+ virtual std::string GetAccountId() const OVERRIDE;
+
+ private:
+ OAuth2TokenServiceRequest(const std::string& account_id);
+
+ void StartWithCore(const scoped_refptr<Core>& core);
+
+ const std::string account_id_;
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2TokenServiceRequest);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_REQUEST_H_
diff --git a/chromium/google_apis/gaia/oauth2_token_service_request_unittest.cc b/chromium/google_apis/gaia/oauth2_token_service_request_unittest.cc
new file mode 100644
index 00000000000..0a86cfd0e2d
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_token_service_request_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright 2014 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 "google_apis/gaia/oauth2_token_service_request.h"
+
+#include <set>
+#include <string>
+#include <vector>
+#include "base/threading/thread.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kAccessToken[] = "access_token";
+const char kAccountId[] = "test_user@gmail.com";
+const char kScope[] = "SCOPE";
+
+class TestingOAuth2TokenServiceConsumer : public OAuth2TokenService::Consumer {
+ public:
+ TestingOAuth2TokenServiceConsumer();
+ virtual ~TestingOAuth2TokenServiceConsumer();
+
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ int num_get_token_success_;
+ int num_get_token_failure_;
+ std::string last_token_;
+ GoogleServiceAuthError last_error_;
+};
+
+TestingOAuth2TokenServiceConsumer::TestingOAuth2TokenServiceConsumer()
+ : OAuth2TokenService::Consumer("test"),
+ num_get_token_success_(0),
+ num_get_token_failure_(0),
+ last_error_(GoogleServiceAuthError::AuthErrorNone()) {
+}
+
+TestingOAuth2TokenServiceConsumer::~TestingOAuth2TokenServiceConsumer() {
+}
+
+void TestingOAuth2TokenServiceConsumer::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& token,
+ const base::Time& expiration_date) {
+ last_token_ = token;
+ ++num_get_token_success_;
+}
+
+void TestingOAuth2TokenServiceConsumer::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ last_error_ = error;
+ ++num_get_token_failure_;
+}
+
+// A mock implementation of an OAuth2TokenService.
+//
+// Use SetResponse to vary the response to token requests.
+class MockOAuth2TokenService : public FakeOAuth2TokenService {
+ public:
+ MockOAuth2TokenService();
+ virtual ~MockOAuth2TokenService();
+
+ void SetResponse(const GoogleServiceAuthError& error,
+ const std::string& access_token,
+ const base::Time& expiration);
+
+ int num_invalidate_token() const { return num_invalidate_token_; }
+
+ const std::string& last_token_invalidated() const {
+ return last_token_invalidated_;
+ }
+
+ protected:
+ virtual void FetchOAuth2Token(RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) OVERRIDE;
+
+ virtual void InvalidateOAuth2Token(const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) OVERRIDE;
+
+ private:
+ GoogleServiceAuthError response_error_;
+ std::string response_access_token_;
+ base::Time response_expiration_;
+ int num_invalidate_token_;
+ std::string last_token_invalidated_;
+};
+
+MockOAuth2TokenService::MockOAuth2TokenService()
+ : response_error_(GoogleServiceAuthError::AuthErrorNone()),
+ response_access_token_(kAccessToken),
+ response_expiration_(base::Time::Max()),
+ num_invalidate_token_(0) {
+}
+
+MockOAuth2TokenService::~MockOAuth2TokenService() {
+}
+
+void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError& error,
+ const std::string& access_token,
+ const base::Time& expiration) {
+ response_error_ = error;
+ response_access_token_ = access_token;
+ response_expiration_ = expiration;
+}
+
+void MockOAuth2TokenService::FetchOAuth2Token(
+ RequestImpl* request,
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
+ request->AsWeakPtr(),
+ response_error_,
+ response_access_token_,
+ response_expiration_));
+}
+
+void MockOAuth2TokenService::InvalidateOAuth2Token(
+ const std::string& account_id,
+ const std::string& client_id,
+ const ScopeSet& scopes,
+ const std::string& access_token) {
+ ++num_invalidate_token_;
+ last_token_invalidated_ = access_token;
+}
+
+class OAuth2TokenServiceRequestTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ protected:
+ class Provider : public OAuth2TokenServiceRequest::TokenServiceProvider {
+ public:
+ Provider(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ OAuth2TokenService* token_service);
+
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetTokenServiceTaskRunner() OVERRIDE;
+ virtual OAuth2TokenService* GetTokenService() OVERRIDE;
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ OAuth2TokenService* token_service_;
+ };
+
+ base::MessageLoop ui_loop_;
+ OAuth2TokenService::ScopeSet scopes_;
+ scoped_ptr<MockOAuth2TokenService> oauth2_service_;
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider> provider_;
+ TestingOAuth2TokenServiceConsumer consumer_;
+};
+
+void OAuth2TokenServiceRequestTest::SetUp() {
+ scopes_.insert(kScope);
+ oauth2_service_.reset(new MockOAuth2TokenService);
+ oauth2_service_->AddAccount(kAccountId);
+ provider_.reset(
+ new Provider(base::MessageLoopProxy::current(), oauth2_service_.get()));
+}
+
+void OAuth2TokenServiceRequestTest::TearDown() {
+ // Run the loop to execute any pending tasks that may free resources.
+ ui_loop_.RunUntilIdle();
+}
+
+OAuth2TokenServiceRequestTest::Provider::Provider(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ OAuth2TokenService* token_service)
+ : task_runner_(task_runner), token_service_(token_service) {
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+OAuth2TokenServiceRequestTest::Provider::GetTokenServiceTaskRunner() {
+ return task_runner_;
+}
+
+OAuth2TokenService* OAuth2TokenServiceRequestTest::Provider::GetTokenService() {
+ return token_service_;
+}
+
+TEST_F(OAuth2TokenServiceRequestTest, CreateAndStart_Failure) {
+ oauth2_service_->SetResponse(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE),
+ std::string(),
+ base::Time());
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ OAuth2TokenServiceRequest::CreateAndStart(
+ provider_.get(), kAccountId, scopes_, &consumer_));
+ ui_loop_.RunUntilIdle();
+ EXPECT_EQ(0, consumer_.num_get_token_success_);
+ EXPECT_EQ(1, consumer_.num_get_token_failure_);
+ EXPECT_EQ(GoogleServiceAuthError::SERVICE_UNAVAILABLE,
+ consumer_.last_error_.state());
+ EXPECT_EQ(0, oauth2_service_->num_invalidate_token());
+}
+
+TEST_F(OAuth2TokenServiceRequestTest, CreateAndStart_Success) {
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ OAuth2TokenServiceRequest::CreateAndStart(
+ provider_.get(), kAccountId, scopes_, &consumer_));
+ ui_loop_.RunUntilIdle();
+ EXPECT_EQ(1, consumer_.num_get_token_success_);
+ EXPECT_EQ(0, consumer_.num_get_token_failure_);
+ EXPECT_EQ(kAccessToken, consumer_.last_token_);
+ EXPECT_EQ(0, oauth2_service_->num_invalidate_token());
+}
+
+TEST_F(OAuth2TokenServiceRequestTest,
+ CreateAndStart_DestroyRequestBeforeCompletes) {
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ OAuth2TokenServiceRequest::CreateAndStart(
+ provider_.get(), kAccountId, scopes_, &consumer_));
+ request.reset();
+ ui_loop_.RunUntilIdle();
+ EXPECT_EQ(0, consumer_.num_get_token_success_);
+ EXPECT_EQ(0, consumer_.num_get_token_failure_);
+ EXPECT_EQ(0, oauth2_service_->num_invalidate_token());
+}
+
+TEST_F(OAuth2TokenServiceRequestTest,
+ CreateAndStart_DestroyRequestAfterCompletes) {
+ scoped_ptr<OAuth2TokenServiceRequest> request(
+ OAuth2TokenServiceRequest::CreateAndStart(
+ provider_.get(), kAccountId, scopes_, &consumer_));
+ ui_loop_.RunUntilIdle();
+ request.reset();
+ EXPECT_EQ(1, consumer_.num_get_token_success_);
+ EXPECT_EQ(0, consumer_.num_get_token_failure_);
+ EXPECT_EQ(kAccessToken, consumer_.last_token_);
+ EXPECT_EQ(0, oauth2_service_->num_invalidate_token());
+}
+
+TEST_F(OAuth2TokenServiceRequestTest, InvalidateToken) {
+ OAuth2TokenServiceRequest::InvalidateToken(
+ provider_.get(), kAccountId, scopes_, kAccessToken);
+ ui_loop_.RunUntilIdle();
+ EXPECT_EQ(0, consumer_.num_get_token_success_);
+ EXPECT_EQ(0, consumer_.num_get_token_failure_);
+ EXPECT_EQ(kAccessToken, oauth2_service_->last_token_invalidated());
+ EXPECT_EQ(1, oauth2_service_->num_invalidate_token());
+}
+
+} // namespace
diff --git a/chromium/google_apis/gaia/oauth2_token_service_test_util.cc b/chromium/google_apis/gaia/oauth2_token_service_test_util.cc
index 2aae59bb41a..6dbed5f8974 100644
--- a/chromium/google_apis/gaia/oauth2_token_service_test_util.cc
+++ b/chromium/google_apis/gaia/oauth2_token_service_test_util.cc
@@ -20,7 +20,8 @@ std::string GetValidTokenResponse(std::string token, int expiration) {
}
TestingOAuth2TokenServiceConsumer::TestingOAuth2TokenServiceConsumer()
- : number_of_successful_tokens_(0),
+ : OAuth2TokenService::Consumer("test"),
+ number_of_successful_tokens_(0),
last_error_(GoogleServiceAuthError::AuthErrorNone()),
number_of_errors_(0) {
}
diff --git a/chromium/google_apis/gaia/oauth2_token_service_unittest.cc b/chromium/google_apis/gaia/oauth2_token_service_unittest.cc
index dcd21b12e93..b79e361c44c 100644
--- a/chromium/google_apis/gaia/oauth2_token_service_unittest.cc
+++ b/chromium/google_apis/gaia/oauth2_token_service_unittest.cc
@@ -9,7 +9,7 @@
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
-#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "google_apis/gaia/oauth2_token_service.h"
#include "google_apis/gaia/oauth2_token_service_test_util.h"
#include "net/http/http_status_code.h"
@@ -56,14 +56,19 @@ class TestOAuth2TokenService : public OAuth2TokenService {
// For testing: set the refresh token to be used.
void set_refresh_token(const std::string& account_id,
const std::string& refresh_token) {
- refresh_tokens_[account_id] = refresh_token;
+ if (refresh_token.empty())
+ refresh_tokens_.erase(account_id);
+ else
+ refresh_tokens_[account_id] = refresh_token;
}
- protected:
- virtual std::string GetRefreshToken(const std::string& account_id) OVERRIDE {
- // account_id explicitly ignored.
- return refresh_tokens_[account_id];
- }
+ virtual bool RefreshTokenIsAvailable(const std::string& account_id) const
+ OVERRIDE {
+ std::map<std::string, std::string>::const_iterator it =
+ refresh_tokens_.find(account_id);
+
+ return it != refresh_tokens_.end();
+ };
private:
// OAuth2TokenService implementation.
@@ -71,6 +76,17 @@ class TestOAuth2TokenService : public OAuth2TokenService {
return request_context_getter_.get();
}
+ virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) OVERRIDE {
+ std::map<std::string, std::string>::const_iterator it =
+ refresh_tokens_.find(account_id);
+ DCHECK(it != refresh_tokens_.end());
+ std::string refresh_token(it->second);
+ return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token);
+ };
+
std::map<std::string, std::string> refresh_tokens_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
};
diff --git a/chromium/google_apis/gaia/ubertoken_fetcher.cc b/chromium/google_apis/gaia/ubertoken_fetcher.cc
new file mode 100644
index 00000000000..da0ff27c14c
--- /dev/null
+++ b/chromium/google_apis/gaia/ubertoken_fetcher.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 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 "google_apis/gaia/ubertoken_fetcher.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+UbertokenFetcher::UbertokenFetcher(
+ OAuth2TokenService* token_service,
+ UbertokenConsumer* consumer,
+ net::URLRequestContextGetter* request_context)
+ : OAuth2TokenService::Consumer("uber_token_fetcher"),
+ token_service_(token_service),
+ consumer_(consumer),
+ request_context_(request_context) {
+ DCHECK(token_service);
+ DCHECK(consumer);
+ DCHECK(request_context);
+}
+
+UbertokenFetcher::~UbertokenFetcher() {
+}
+
+void UbertokenFetcher::StartFetchingToken(const std::string& account_id) {
+ OAuth2TokenService::ScopeSet scopes;
+ scopes.insert(GaiaConstants::kOAuth1LoginScope);
+ access_token_request_ =
+ token_service_->StartRequest(account_id, scopes, this);
+}
+
+void UbertokenFetcher::OnUberAuthTokenSuccess(const std::string& token) {
+ consumer_->OnUbertokenSuccess(token);
+}
+
+void UbertokenFetcher::OnUberAuthTokenFailure(
+ const GoogleServiceAuthError& error) {
+ consumer_->OnUbertokenFailure(error);
+}
+
+void UbertokenFetcher::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ access_token_request_.reset();
+ gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this,
+ GaiaConstants::kChromeSource,
+ request_context_));
+ gaia_auth_fetcher_->StartTokenFetchForUberAuthExchange(access_token);
+}
+
+void UbertokenFetcher::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ access_token_request_.reset();
+ consumer_->OnUbertokenFailure(error);
+}
diff --git a/chromium/google_apis/gaia/ubertoken_fetcher.h b/chromium/google_apis/gaia/ubertoken_fetcher.h
new file mode 100644
index 00000000000..acc4bb4e1a7
--- /dev/null
+++ b/chromium/google_apis/gaia/ubertoken_fetcher.h
@@ -0,0 +1,71 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GAIA_UBERTOKEN_FETCHER_H_
+#define GOOGLE_APIS_GAIA_UBERTOKEN_FETCHER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+// Allow to retrieves an uber-auth token for the user. This class uses the
+// |OAuth2TokenService| and considers that the user is already logged in. It
+// will use the OAuth2 access token to generate the uber-auth token.
+//
+// This class should be used on a single thread, but it can be whichever thread
+// that you like.
+//
+// This class can handle one request at a time.
+
+class GaiaAuthFetcher;
+class GoogleServiceAuthError;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Callback for the |UbertokenFetcher| class.
+class UbertokenConsumer {
+ public:
+ UbertokenConsumer() {}
+ virtual ~UbertokenConsumer() {}
+ virtual void OnUbertokenSuccess(const std::string& token) {}
+ virtual void OnUbertokenFailure(const GoogleServiceAuthError& error) {}
+};
+
+// Allows to retrieve an uber-auth token.
+class UbertokenFetcher : public GaiaAuthConsumer,
+ public OAuth2TokenService::Consumer {
+ public:
+ UbertokenFetcher(OAuth2TokenService* token_service,
+ UbertokenConsumer* consumer,
+ net::URLRequestContextGetter* request_context);
+ virtual ~UbertokenFetcher();
+
+ // Start fetching the token for |account_id|.
+ virtual void StartFetchingToken(const std::string& account_id);
+
+ // Overriden from GaiaAuthConsumer
+ virtual void OnUberAuthTokenSuccess(const std::string& token) OVERRIDE;
+ virtual void OnUberAuthTokenFailure(
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // Overriden from OAuth2TokenService::Consumer:
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ private:
+ OAuth2TokenService* token_service_;
+ UbertokenConsumer* consumer_;
+ net::URLRequestContextGetter* request_context_;
+ scoped_ptr<GaiaAuthFetcher> gaia_auth_fetcher_;
+ scoped_ptr<OAuth2TokenService::Request> access_token_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(UbertokenFetcher);
+};
+
+#endif // GOOGLE_APIS_GAIA_UBERTOKEN_FETCHER_H_
diff --git a/chromium/google_apis/gaia/ubertoken_fetcher_unittest.cc b/chromium/google_apis/gaia/ubertoken_fetcher_unittest.cc
new file mode 100644
index 00000000000..8352ee8cab9
--- /dev/null
+++ b/chromium/google_apis/gaia/ubertoken_fetcher_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 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 "google_apis/gaia/ubertoken_fetcher.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "google_apis/gaia/fake_oauth2_token_service.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestAccountId[] = "test@gmail.com";
+
+class MockUbertokenConsumer : public UbertokenConsumer {
+ public:
+ MockUbertokenConsumer()
+ : nb_correct_token_(0),
+ last_error_(GoogleServiceAuthError::AuthErrorNone()),
+ nb_error_(0) {
+ }
+ virtual ~MockUbertokenConsumer() {}
+
+ virtual void OnUbertokenSuccess(const std::string& token) OVERRIDE {
+ last_token_ = token;
+ ++ nb_correct_token_;
+ }
+
+ virtual void OnUbertokenFailure(const GoogleServiceAuthError& error)
+ OVERRIDE {
+ last_error_ = error;
+ ++nb_error_;
+ }
+
+ std::string last_token_;
+ int nb_correct_token_;
+ GoogleServiceAuthError last_error_;
+ int nb_error_;
+};
+
+} // namespace
+
+class UbertokenFetcherTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ request_context_getter_ = new net::TestURLRequestContextGetter(
+ base::MessageLoopProxy::current());
+ fetcher_.reset(new UbertokenFetcher(&token_service_,
+ &consumer_,
+ request_context_getter_.get()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ fetcher_.reset();
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory factory_;
+ FakeOAuth2TokenService token_service_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ MockUbertokenConsumer consumer_;
+ scoped_ptr<UbertokenFetcher> fetcher_;
+};
+
+TEST_F(UbertokenFetcherTest, Basic) {
+}
+
+TEST_F(UbertokenFetcherTest, Success) {
+ fetcher_->StartFetchingToken(kTestAccountId);
+ fetcher_->OnGetTokenSuccess(NULL, "accessToken", base::Time());
+ fetcher_->OnUberAuthTokenSuccess("uberToken");
+
+ EXPECT_EQ(0, consumer_.nb_error_);
+ EXPECT_EQ(1, consumer_.nb_correct_token_);
+ EXPECT_EQ("uberToken", consumer_.last_token_);
+}
+
+TEST_F(UbertokenFetcherTest, NoRefreshToken) {
+ fetcher_->StartFetchingToken(kTestAccountId);
+ GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
+ fetcher_->OnGetTokenFailure(NULL, error);
+
+ EXPECT_EQ(1, consumer_.nb_error_);
+ EXPECT_EQ(0, consumer_.nb_correct_token_);
+}
+
+TEST_F(UbertokenFetcherTest, FailureToGetAccessToken) {
+ fetcher_->StartFetchingToken(kTestAccountId);
+ GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
+ fetcher_->OnGetTokenFailure(NULL, error);
+
+ EXPECT_EQ(1, consumer_.nb_error_);
+ EXPECT_EQ(0, consumer_.nb_correct_token_);
+ EXPECT_EQ("", consumer_.last_token_);
+}
+
+TEST_F(UbertokenFetcherTest, FailureToGetUberToken) {
+ fetcher_->StartFetchingToken(kTestAccountId);
+ GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
+ fetcher_->OnGetTokenSuccess(NULL, "accessToken", base::Time());
+ fetcher_->OnUberAuthTokenFailure(error);
+
+ EXPECT_EQ(1, consumer_.nb_error_);
+ EXPECT_EQ(0, consumer_.nb_correct_token_);
+ EXPECT_EQ("", consumer_.last_token_);
+}
diff --git a/chromium/google_apis/gcm/DEPS b/chromium/google_apis/gcm/DEPS
index 08ac400e0d4..047dc1564b5 100644
--- a/chromium/google_apis/gcm/DEPS
+++ b/chromium/google_apis/gcm/DEPS
@@ -7,7 +7,6 @@ include_rules = [
"+base",
"+testing",
- "+components/webdata/encryptor",
"+google", # For third_party/protobuf/src.
"+net",
"+third_party/leveldatabase",
diff --git a/chromium/google_apis/gcm/base/encryptor.h b/chromium/google_apis/gcm/base/encryptor.h
new file mode 100644
index 00000000000..898e7dba77e
--- /dev/null
+++ b/chromium/google_apis/gcm/base/encryptor.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENCRYPTOR_H_
+#define GOOGLE_APIS_GCM_ENCRYPTOR_H_
+
+#include <string>
+#include "google_apis/gcm/base/gcm_export.h"
+
+namespace gcm {
+
+class GCM_EXPORT Encryptor {
+ public:
+ // All methods below should be thread-safe.
+ virtual bool EncryptString(const std::string& plaintext,
+ std::string* ciphertext) = 0;
+
+ virtual bool DecryptString(const std::string& ciphertext,
+ std::string* plaintext) = 0;
+
+ virtual ~Encryptor() {}
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENCRYPTOR_H_
diff --git a/chromium/google_apis/gcm/base/fake_encryptor.cc b/chromium/google_apis/gcm/base/fake_encryptor.cc
new file mode 100644
index 00000000000..ac58081ee4c
--- /dev/null
+++ b/chromium/google_apis/gcm/base/fake_encryptor.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2014 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 "google_apis/gcm/base/fake_encryptor.h"
+
+#include "base/base64.h"
+
+namespace gcm {
+
+FakeEncryptor::~FakeEncryptor() {}
+
+bool FakeEncryptor::EncryptString(const std::string& plaintext,
+ std::string* ciphertext) {
+ base::Base64Encode(plaintext, ciphertext);
+ return true;
+}
+
+bool FakeEncryptor::DecryptString(const std::string& ciphertext,
+ std::string* plaintext) {
+ return base::Base64Decode(ciphertext, plaintext);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/base/fake_encryptor.h b/chromium/google_apis/gcm/base/fake_encryptor.h
new file mode 100644
index 00000000000..01ae67b0e85
--- /dev/null
+++ b/chromium/google_apis/gcm/base/fake_encryptor.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_BASE_ENCRYPTOR_H_
+#define GOOGLE_APIS_GCM_BASE_ENCRYPTOR_H_
+
+#include "base/compiler_specific.h"
+#include "google_apis/gcm/base/encryptor.h"
+
+namespace gcm {
+
+// Encryptor which simply base64-encodes the plaintext to get the
+// ciphertext. Obviously, this should be used only for testing.
+class FakeEncryptor : public Encryptor {
+ public:
+ virtual ~FakeEncryptor();
+
+ virtual bool EncryptString(const std::string& plaintext,
+ std::string* ciphertext) OVERRIDE;
+
+ virtual bool DecryptString(const std::string& ciphertext,
+ std::string* plaintext) OVERRIDE;
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_BASE_ENCRYPTOR_H_
diff --git a/chromium/google_apis/gcm/base/mcs_message_unittest.cc b/chromium/google_apis/gcm/base/mcs_message_unittest.cc
index 4d4ef598ad2..55ab49d130f 100644
--- a/chromium/google_apis/gcm/base/mcs_message_unittest.cc
+++ b/chromium/google_apis/gcm/base/mcs_message_unittest.cc
@@ -36,7 +36,7 @@ TEST_F(MCSMessageTest, Invalid) {
TEST_F(MCSMessageTest, InitInferTag) {
scoped_ptr<mcs_proto::LoginRequest> login_request(
- BuildLoginRequest(kAndroidId, kSecret));
+ BuildLoginRequest(kAndroidId, kSecret, ""));
scoped_ptr<google::protobuf::MessageLite> login_copy(
new mcs_proto::LoginRequest(*login_request));
MCSMessage message(*login_copy);
@@ -54,7 +54,7 @@ TEST_F(MCSMessageTest, InitInferTag) {
TEST_F(MCSMessageTest, InitWithTag) {
scoped_ptr<mcs_proto::LoginRequest> login_request(
- BuildLoginRequest(kAndroidId, kSecret));
+ BuildLoginRequest(kAndroidId, kSecret, ""));
scoped_ptr<google::protobuf::MessageLite> login_copy(
new mcs_proto::LoginRequest(*login_request));
MCSMessage message(kLoginRequestTag, *login_copy);
@@ -72,7 +72,7 @@ TEST_F(MCSMessageTest, InitWithTag) {
TEST_F(MCSMessageTest, InitPassOwnership) {
scoped_ptr<mcs_proto::LoginRequest> login_request(
- BuildLoginRequest(kAndroidId, kSecret));
+ BuildLoginRequest(kAndroidId, kSecret, ""));
scoped_ptr<google::protobuf::MessageLite> login_copy(
new mcs_proto::LoginRequest(*login_request));
MCSMessage message(kLoginRequestTag,
diff --git a/chromium/google_apis/gcm/base/mcs_util.cc b/chromium/google_apis/gcm/base/mcs_util.cc
index 736556079f8..b578e573922 100644
--- a/chromium/google_apis/gcm/base/mcs_util.cc
+++ b/chromium/google_apis/gcm/base/mcs_util.cc
@@ -8,6 +8,8 @@
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
namespace gcm {
@@ -37,17 +39,21 @@ const char* kProtoNames[] = {
COMPILE_ASSERT(arraysize(kProtoNames) == kNumProtoTypes,
ProtoNamesMustIncludeAllTags);
-// TODO(zea): replace these with proper values.
-const char kLoginId[] = "login-1";
+const char kLoginId[] = "chrome-";
const char kLoginDomain[] = "mcs.android.com";
const char kLoginDeviceIdPrefix[] = "android-";
-const char kLoginSettingName[] = "new_vc";
-const char kLoginSettingValue[] = "1";
+const char kLoginSettingDefaultName[] = "new_vc";
+const char kLoginSettingDefaultValue[] = "1";
+
+// Maximum amount of time to save an unsent outgoing message for.
+const int kMaxTTLSeconds = 4 * 7 * 24 * 60 * 60; // 4 weeks.
} // namespace
-scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(uint64 auth_id,
- uint64 auth_token) {
+scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(
+ uint64 auth_id,
+ uint64 auth_token,
+ const std::string& version_string) {
// Create a hex encoded auth id for the device id field.
std::string auth_id_hex;
auth_id_hex = base::StringPrintf("%" PRIx64, auth_id);
@@ -58,12 +64,10 @@ scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(uint64 auth_id,
scoped_ptr<mcs_proto::LoginRequest> login_request(
new mcs_proto::LoginRequest());
- // TODO(zea): set better values.
- login_request->set_account_id(1000000);
login_request->set_adaptive_heartbeat(false);
login_request->set_auth_service(mcs_proto::LoginRequest::ANDROID_ID);
login_request->set_auth_token(auth_token_str);
- login_request->set_id(kLoginId);
+ login_request->set_id(kLoginId + version_string);
login_request->set_domain(kLoginDomain);
login_request->set_device_id(kLoginDeviceIdPrefix + auth_id_hex);
login_request->set_network_type(1);
@@ -72,8 +76,8 @@ scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(uint64 auth_id,
login_request->set_use_rmq2(true);
login_request->add_setting();
- login_request->mutable_setting(0)->set_name(kLoginSettingName);
- login_request->mutable_setting(0)->set_value(kLoginSettingValue);
+ login_request->mutable_setting(0)->set_name(kLoginSettingDefaultName);
+ login_request->mutable_setting(0)->set_value(kLoginSettingDefaultValue);
return login_request.Pass();
}
@@ -209,25 +213,50 @@ void SetLastStreamIdReceived(uint32 val,
if (protobuf->GetTypeName() == kProtoNames[kIqStanzaTag]) {
reinterpret_cast<mcs_proto::IqStanza*>(protobuf)->
set_last_stream_id_received(val);
- return;
+ return;
} else if (protobuf->GetTypeName() == kProtoNames[kHeartbeatPingTag]) {
reinterpret_cast<mcs_proto::HeartbeatPing*>(protobuf)->
set_last_stream_id_received(val);
- return;
+ return;
} else if (protobuf->GetTypeName() == kProtoNames[kHeartbeatAckTag]) {
reinterpret_cast<mcs_proto::HeartbeatAck*>(protobuf)->
set_last_stream_id_received(val);
- return;
+ return;
} else if (protobuf->GetTypeName() == kProtoNames[kDataMessageStanzaTag]) {
reinterpret_cast<mcs_proto::DataMessageStanza*>(protobuf)->
set_last_stream_id_received(val);
- return;
+ return;
} else if (protobuf->GetTypeName() == kProtoNames[kLoginResponseTag]) {
reinterpret_cast<mcs_proto::LoginResponse*>(protobuf)->
set_last_stream_id_received(val);
- return;
+ return;
}
NOTREACHED();
}
+bool HasTTLExpired(const google::protobuf::MessageLite& protobuf,
+ base::Clock* clock) {
+ if (protobuf.GetTypeName() != kProtoNames[kDataMessageStanzaTag])
+ return false;
+ uint64 ttl = GetTTL(protobuf);
+ uint64 sent =
+ reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf)->sent();
+ DCHECK(sent);
+ return ttl > 0 &&
+ clock->Now() >
+ base::Time::FromInternalValue(
+ (sent + ttl) * base::Time::kMicrosecondsPerSecond);
+}
+
+int GetTTL(const google::protobuf::MessageLite& protobuf) {
+ if (protobuf.GetTypeName() != kProtoNames[kDataMessageStanzaTag])
+ return 0;
+ const mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf);
+ if (!data_message->has_ttl())
+ return kMaxTTLSeconds;
+ DCHECK_LE(data_message->ttl(), kMaxTTLSeconds);
+ return data_message->ttl();
+}
+
} // namespace gcm
diff --git a/chromium/google_apis/gcm/base/mcs_util.h b/chromium/google_apis/gcm/base/mcs_util.h
index 7f92564ad27..95d97f29489 100644
--- a/chromium/google_apis/gcm/base/mcs_util.h
+++ b/chromium/google_apis/gcm/base/mcs_util.h
@@ -8,6 +8,7 @@
#define GOOGLE_APIS_GCM_BASE_MCS_UTIL_H_
#include <string>
+#include <vector>
#include "base/basictypes.h"
#include "base/memory/ref_counted.h"
@@ -15,6 +16,10 @@
#include "google_apis/gcm/base/gcm_export.h"
#include "google_apis/gcm/protocol/mcs.pb.h"
+namespace base {
+class Clock;
+}
+
namespace net {
class StreamSocket;
}
@@ -52,7 +57,8 @@ enum MCSIqStanzaExtension {
// Builds a LoginRequest with the hardcoded local data.
GCM_EXPORT scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(
uint64 auth_id,
- uint64 auth_token);
+ uint64 auth_token,
+ const std::string& version_string);
// Builds a StreamAck IqStanza message.
GCM_EXPORT scoped_ptr<mcs_proto::IqStanza> BuildStreamAck();
@@ -76,6 +82,13 @@ GCM_EXPORT void SetLastStreamIdReceived(
uint32 last_stream_id_received,
google::protobuf::MessageLite* protobuf);
+// Returns whether the TTL (time to live) for this message has expired, based
+// on the |sent| timestamps and base::TimeTicks::Now(). If |protobuf| is not
+// for a DataMessageStanza or the TTL is 0, will return false.
+GCM_EXPORT bool HasTTLExpired(const google::protobuf::MessageLite& protobuf,
+ base::Clock* clock);
+GCM_EXPORT int GetTTL(const google::protobuf::MessageLite& protobuf);
+
} // namespace gcm
#endif // GOOGLE_APIS_GCM_BASE_MCS_UTIL_H_
diff --git a/chromium/google_apis/gcm/base/mcs_util_unittest.cc b/chromium/google_apis/gcm/base/mcs_util_unittest.cc
index d25914583a4..00bac1175f9 100644
--- a/chromium/google_apis/gcm/base/mcs_util_unittest.cc
+++ b/chromium/google_apis/gcm/base/mcs_util_unittest.cc
@@ -19,11 +19,13 @@ const uint64 kAuthToken = 12345;
// Build a login request protobuf.
TEST(MCSUtilTest, BuildLoginRequest) {
scoped_ptr<mcs_proto::LoginRequest> login_request =
- BuildLoginRequest(kAuthId, kAuthToken);
- ASSERT_EQ("login-1", login_request->id());
+ BuildLoginRequest(kAuthId, kAuthToken, "1.0");
+ ASSERT_EQ("chrome-1.0", login_request->id());
ASSERT_EQ(base::Uint64ToString(kAuthToken), login_request->auth_token());
ASSERT_EQ(base::Uint64ToString(kAuthId), login_request->user());
ASSERT_EQ("android-3d5c23dac2a1fa7c", login_request->device_id());
+ ASSERT_EQ("new_vc", login_request->setting(0).name());
+ ASSERT_EQ("1", login_request->setting(0).value());
// TODO(zea): test the other fields once they have valid values.
}
diff --git a/chromium/google_apis/gcm/base/socket_stream.cc b/chromium/google_apis/gcm/base/socket_stream.cc
index 1a0b29d8d07..8c152c6c5b3 100644
--- a/chromium/google_apis/gcm/base/socket_stream.cc
+++ b/chromium/google_apis/gcm/base/socket_stream.cc
@@ -89,8 +89,8 @@ net::Error SocketInputStream::Refresh(const base::Closure& callback,
DCHECK_GT(byte_limit, 0);
if (byte_limit > read_buffer_->BytesRemaining()) {
- NOTREACHED() << "Out of buffer space, closing input stream.";
- CloseStream(net::ERR_UNEXPECTED, base::Closure());
+ LOG(ERROR) << "Out of buffer space, closing input stream.";
+ CloseStream(net::ERR_FILE_TOO_BIG, base::Closure());
return net::OK;
}
diff --git a/chromium/google_apis/gcm/engine/checkin_request.cc b/chromium/google_apis/gcm/engine/checkin_request.cc
new file mode 100644
index 00000000000..a5558471772
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/checkin_request.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 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 "google_apis/gcm/engine/checkin_request.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
+#include "google_apis/gcm/protocol/checkin.pb.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_status.h"
+
+namespace gcm {
+
+namespace {
+const char kRequestContentType[] = "application/x-protobuf";
+const int kRequestVersionValue = 3;
+const int kDefaultUserSerialNumber = 0;
+
+// This enum is also used in an UMA histogram (GCMCheckinRequestStatus
+// enum defined in tools/metrics/histograms/histogram.xml). Hence the entries
+// here shouldn't be deleted or re-ordered and new ones should be added to
+// the end, and update the GetCheckinRequestStatusString(...) below.
+enum CheckinRequestStatus {
+ SUCCESS, // Checkin completed successfully.
+ URL_FETCHING_FAILED, // URL fetching failed.
+ HTTP_BAD_REQUEST, // The request was malformed.
+ HTTP_UNAUTHORIZED, // The security token didn't match the android id.
+ HTTP_NOT_OK, // HTTP status was not OK.
+ RESPONSE_PARSING_FAILED, // Check in response parsing failed.
+ ZERO_ID_OR_TOKEN, // Either returned android id or security token
+ // was zero.
+ // NOTE: always keep this entry at the end. Add new status types only
+ // immediately above this line. Make sure to update the corresponding
+ // histogram enum accordingly.
+ STATUS_COUNT
+};
+
+// Returns string representation of enum CheckinRequestStatus.
+std::string GetCheckinRequestStatusString(CheckinRequestStatus status) {
+ switch (status) {
+ case SUCCESS:
+ return "SUCCESS";
+ case URL_FETCHING_FAILED:
+ return "URL_FETCHING_FAILED";
+ case HTTP_BAD_REQUEST:
+ return "HTTP_BAD_REQUEST";
+ case HTTP_UNAUTHORIZED:
+ return "HTTP_UNAUTHORIZED";
+ case HTTP_NOT_OK:
+ return "HTTP_NOT_OK";
+ case RESPONSE_PARSING_FAILED:
+ return "RESPONSE_PARSING_FAILED";
+ case ZERO_ID_OR_TOKEN:
+ return "ZERO_ID_OR_TOKEN";
+ default:
+ NOTREACHED();
+ return "UNKNOWN_STATUS";
+ }
+}
+
+// Records checkin status to both stats recorder and reports to UMA.
+void RecordCheckinStatusAndReportUMA(CheckinRequestStatus status,
+ GCMStatsRecorder* recorder,
+ bool will_retry) {
+ UMA_HISTOGRAM_ENUMERATION("GCM.CheckinRequestStatus", status, STATUS_COUNT);
+ if (status == SUCCESS)
+ recorder->RecordCheckinSuccess();
+ else {
+ recorder->RecordCheckinFailure(GetCheckinRequestStatusString(status),
+ will_retry);
+ }
+}
+
+} // namespace
+
+CheckinRequest::RequestInfo::RequestInfo(
+ uint64 android_id,
+ uint64 security_token,
+ const std::string& settings_digest,
+ const checkin_proto::ChromeBuildProto& chrome_build_proto)
+ : android_id(android_id),
+ security_token(security_token),
+ settings_digest(settings_digest),
+ chrome_build_proto(chrome_build_proto) {
+}
+
+CheckinRequest::RequestInfo::~RequestInfo() {}
+
+CheckinRequest::CheckinRequest(
+ const GURL& checkin_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const CheckinRequestCallback& callback,
+ net::URLRequestContextGetter* request_context_getter,
+ GCMStatsRecorder* recorder)
+ : request_context_getter_(request_context_getter),
+ callback_(callback),
+ backoff_entry_(&backoff_policy),
+ checkin_url_(checkin_url),
+ request_info_(request_info),
+ recorder_(recorder),
+ weak_ptr_factory_(this) {
+}
+
+CheckinRequest::~CheckinRequest() {}
+
+void CheckinRequest::Start() {
+ DCHECK(!url_fetcher_.get());
+
+ checkin_proto::AndroidCheckinRequest request;
+ request.set_id(request_info_.android_id);
+ request.set_security_token(request_info_.security_token);
+ request.set_user_serial_number(kDefaultUserSerialNumber);
+ request.set_version(kRequestVersionValue);
+ if (!request_info_.settings_digest.empty())
+ request.set_digest(request_info_.settings_digest);
+
+ checkin_proto::AndroidCheckinProto* checkin = request.mutable_checkin();
+ checkin->mutable_chrome_build()->CopyFrom(request_info_.chrome_build_proto);
+#if defined(CHROME_OS)
+ checkin->set_type(checkin_proto::DEVICE_CHROME_OS);
+#else
+ checkin->set_type(checkin_proto::DEVICE_CHROME_BROWSER);
+#endif
+
+ std::string upload_data;
+ CHECK(request.SerializeToString(&upload_data));
+
+ url_fetcher_.reset(
+ net::URLFetcher::Create(checkin_url_, net::URLFetcher::POST, this));
+ url_fetcher_->SetRequestContext(request_context_getter_);
+ url_fetcher_->SetUploadData(kRequestContentType, upload_data);
+ recorder_->RecordCheckinInitiated(request_info_.android_id);
+ request_start_time_ = base::TimeTicks::Now();
+ url_fetcher_->Start();
+}
+
+void CheckinRequest::RetryWithBackoff(bool update_backoff) {
+ if (update_backoff) {
+ backoff_entry_.InformOfRequest(false);
+ url_fetcher_.reset();
+ }
+
+ if (backoff_entry_.ShouldRejectRequest()) {
+ DVLOG(1) << "Delay GCM checkin for: "
+ << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
+ << " milliseconds.";
+ recorder_->RecordCheckinDelayedDueToBackoff(
+ backoff_entry_.GetTimeUntilRelease().InMilliseconds());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&CheckinRequest::RetryWithBackoff,
+ weak_ptr_factory_.GetWeakPtr(),
+ false),
+ backoff_entry_.GetTimeUntilRelease());
+ return;
+ }
+
+ Start();
+}
+
+void CheckinRequest::OnURLFetchComplete(const net::URLFetcher* source) {
+ std::string response_string;
+ checkin_proto::AndroidCheckinResponse response_proto;
+ if (!source->GetStatus().is_success()) {
+ LOG(ERROR) << "Failed to get checkin response. Fetcher failed. Retrying.";
+ RecordCheckinStatusAndReportUMA(URL_FETCHING_FAILED, recorder_, true);
+ RetryWithBackoff(true);
+ return;
+ }
+
+ net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>(
+ source->GetResponseCode());
+ if (response_status == net::HTTP_BAD_REQUEST ||
+ response_status == net::HTTP_UNAUTHORIZED) {
+ // BAD_REQUEST indicates that the request was malformed.
+ // UNAUTHORIZED indicates that security token didn't match the android id.
+ LOG(ERROR) << "No point retrying the checkin with status: "
+ << response_status << ". Checkin failed.";
+ CheckinRequestStatus status = response_status == net::HTTP_BAD_REQUEST ?
+ HTTP_BAD_REQUEST : HTTP_UNAUTHORIZED;
+ RecordCheckinStatusAndReportUMA(status, recorder_, false);
+ callback_.Run(response_proto);
+ return;
+ }
+
+ if (response_status != net::HTTP_OK ||
+ !source->GetResponseAsString(&response_string) ||
+ !response_proto.ParseFromString(response_string)) {
+ LOG(ERROR) << "Failed to get checkin response. HTTP Status: "
+ << response_status << ". Retrying.";
+ CheckinRequestStatus status = response_status != net::HTTP_OK ?
+ HTTP_NOT_OK : RESPONSE_PARSING_FAILED;
+ RecordCheckinStatusAndReportUMA(status, recorder_, true);
+ RetryWithBackoff(true);
+ return;
+ }
+
+ if (!response_proto.has_android_id() ||
+ !response_proto.has_security_token() ||
+ response_proto.android_id() == 0 ||
+ response_proto.security_token() == 0) {
+ LOG(ERROR) << "Android ID or security token is 0. Retrying.";
+ RecordCheckinStatusAndReportUMA(ZERO_ID_OR_TOKEN, recorder_, true);
+ RetryWithBackoff(true);
+ return;
+ }
+
+ RecordCheckinStatusAndReportUMA(SUCCESS, recorder_, false);
+ UMA_HISTOGRAM_COUNTS("GCM.CheckinRetryCount",
+ backoff_entry_.failure_count());
+ UMA_HISTOGRAM_TIMES("GCM.CheckinCompleteTime",
+ base::TimeTicks::Now() - request_start_time_);
+ callback_.Run(response_proto);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/checkin_request.h b/chromium/google_apis/gcm/engine/checkin_request.h
new file mode 100644
index 00000000000..ae4a7d43897
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/checkin_request.h
@@ -0,0 +1,95 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_CHECKIN_REQUEST_H_
+#define GOOGLE_APIS_GCM_ENGINE_CHECKIN_REQUEST_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "google_apis/gcm/protocol/android_checkin.pb.h"
+#include "google_apis/gcm/protocol/checkin.pb.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gcm {
+
+class GCMStatsRecorder;
+
+// Enables making check-in requests with the GCM infrastructure. When called
+// with android_id and security_token both set to 0 it is an initial check-in
+// used to obtain credentials. These should be persisted and used for subsequent
+// check-ins.
+class GCM_EXPORT CheckinRequest : public net::URLFetcherDelegate {
+ public:
+ // A callback function for the checkin request, accepting |checkin_response|
+ // protobuf.
+ typedef base::Callback<void(const checkin_proto::AndroidCheckinResponse&
+ checkin_response)> CheckinRequestCallback;
+
+ // Checkin request details.
+ struct GCM_EXPORT RequestInfo {
+ RequestInfo(uint64 android_id,
+ uint64 security_token,
+ const std::string& settings_digest,
+ const checkin_proto::ChromeBuildProto& chrome_build_proto);
+ ~RequestInfo();
+
+ // Android ID of the device.
+ uint64 android_id;
+ // Security token of the device.
+ uint64 security_token;
+ // Digest of GServices settings on the device.
+ std::string settings_digest;
+ // Information of the Chrome build of this device.
+ checkin_proto::ChromeBuildProto chrome_build_proto;
+ };
+
+ CheckinRequest(const GURL& checkin_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const CheckinRequestCallback& callback,
+ net::URLRequestContextGetter* request_context_getter,
+ GCMStatsRecorder* recorder);
+ virtual ~CheckinRequest();
+
+ void Start();
+
+ // URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ // Schedules a retry attempt, informs the backoff of a previous request's
+ // failure when |update_backoff| is true.
+ void RetryWithBackoff(bool update_backoff);
+
+ net::URLRequestContextGetter* request_context_getter_;
+ CheckinRequestCallback callback_;
+
+ net::BackoffEntry backoff_entry_;
+ GURL checkin_url_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ const RequestInfo request_info_;
+ base::TimeTicks request_start_time_;
+
+ // Recorder that records GCM activities for debugging purpose. Not owned.
+ GCMStatsRecorder* recorder_;
+
+ base::WeakPtrFactory<CheckinRequest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CheckinRequest);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_CHECKIN_REQUEST_H_
diff --git a/chromium/google_apis/gcm/engine/checkin_request_unittest.cc b/chromium/google_apis/gcm/engine/checkin_request_unittest.cc
new file mode 100644
index 00000000000..12e9a601b79
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/checkin_request_unittest.cc
@@ -0,0 +1,380 @@
+// Copyright 2014 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 <string>
+
+#include "google_apis/gcm/engine/checkin_request.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
+#include "google_apis/gcm/protocol/checkin.pb.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ // Explicitly set to 1 to skip the delay of the first Retry, as we are not
+ // trying to test the backoff itself, but rather the fact that retry happens.
+ 1,
+
+ // Initial delay for exponential back-off in ms.
+ 15000, // 15 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.5, // 50%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 60 * 5, // 5 minutes.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+}
+
+const uint64 kAndroidId = 42UL;
+const uint64 kBlankAndroidId = 999999UL;
+const uint64 kBlankSecurityToken = 999999UL;
+const char kCheckinURL[] = "http://foo.bar/checkin";
+const char kChromeVersion[] = "Version String";
+const uint64 kSecurityToken = 77;
+const char kSettingsDigest[] = "settings_digest";
+
+class CheckinRequestTest : public testing::Test {
+ public:
+ enum ResponseScenario {
+ VALID_RESPONSE, // Both android_id and security_token set in response.
+ MISSING_ANDROID_ID, // android_id is missing.
+ MISSING_SECURITY_TOKEN, // security_token is missing.
+ ANDROID_ID_IS_ZER0, // android_id is 0.
+ SECURITY_TOKEN_IS_ZERO // security_token is 0.
+ };
+
+ CheckinRequestTest();
+ virtual ~CheckinRequestTest();
+
+ void FetcherCallback(
+ const checkin_proto::AndroidCheckinResponse& response);
+
+ void CreateRequest(uint64 android_id, uint64 security_token);
+
+ void SetResponseStatusAndString(
+ net::HttpStatusCode status_code,
+ const std::string& response_data);
+
+ void CompleteFetch();
+
+ void SetResponse(ResponseScenario response_scenario);
+
+ protected:
+ bool callback_called_;
+ uint64 android_id_;
+ uint64 security_token_;
+ int checkin_device_type_;
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ scoped_refptr<net::TestURLRequestContextGetter> url_request_context_getter_;
+ checkin_proto::ChromeBuildProto chrome_build_proto_;
+ scoped_ptr<CheckinRequest> request_;
+ FakeGCMStatsRecorder recorder_;
+};
+
+CheckinRequestTest::CheckinRequestTest()
+ : callback_called_(false),
+ android_id_(kBlankAndroidId),
+ security_token_(kBlankSecurityToken),
+ checkin_device_type_(0),
+ url_request_context_getter_(new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())) {
+}
+
+CheckinRequestTest::~CheckinRequestTest() {}
+
+void CheckinRequestTest::FetcherCallback(
+ const checkin_proto::AndroidCheckinResponse& checkin_response) {
+ callback_called_ = true;
+ if (checkin_response.has_android_id())
+ android_id_ = checkin_response.android_id();
+ if (checkin_response.has_security_token())
+ security_token_ = checkin_response.security_token();
+}
+
+void CheckinRequestTest::CreateRequest(uint64 android_id,
+ uint64 security_token) {
+ // First setup a chrome_build protobuf.
+ chrome_build_proto_.set_platform(
+ checkin_proto::ChromeBuildProto::PLATFORM_LINUX);
+ chrome_build_proto_.set_channel(
+ checkin_proto::ChromeBuildProto::CHANNEL_CANARY);
+ chrome_build_proto_.set_chrome_version(kChromeVersion);
+
+ CheckinRequest::RequestInfo request_info(
+ android_id,
+ security_token,
+ kSettingsDigest,
+ chrome_build_proto_);
+ // Then create a request with that protobuf and specified android_id,
+ // security_token.
+ request_.reset(new CheckinRequest(
+ GURL(kCheckinURL),
+ request_info,
+ kDefaultBackoffPolicy,
+ base::Bind(&CheckinRequestTest::FetcherCallback, base::Unretained(this)),
+ url_request_context_getter_.get(),
+ &recorder_));
+
+ // Setting android_id_ and security_token_ to blank value, not used elsewhere
+ // in the tests.
+ callback_called_ = false;
+ android_id_ = kBlankAndroidId;
+ security_token_ = kBlankSecurityToken;
+}
+
+void CheckinRequestTest::SetResponseStatusAndString(
+ net::HttpStatusCode status_code,
+ const std::string& response_data) {
+ net::TestURLFetcher* fetcher =
+ url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(status_code);
+ fetcher->SetResponseString(response_data);
+}
+
+void CheckinRequestTest::CompleteFetch() {
+ net::TestURLFetcher* fetcher =
+ url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+void CheckinRequestTest::SetResponse(ResponseScenario response_scenario) {
+ checkin_proto::AndroidCheckinResponse response;
+ response.set_stats_ok(true);
+
+ uint64 android_id = response_scenario == ANDROID_ID_IS_ZER0 ? 0 : kAndroidId;
+ uint64 security_token =
+ response_scenario == SECURITY_TOKEN_IS_ZERO ? 0 : kSecurityToken;
+
+ if (response_scenario != MISSING_ANDROID_ID)
+ response.set_android_id(android_id);
+
+ if (response_scenario != MISSING_SECURITY_TOKEN)
+ response.set_security_token(security_token);
+
+ std::string response_string;
+ response.SerializeToString(&response_string);
+ SetResponseStatusAndString(net::HTTP_OK, response_string);
+}
+
+TEST_F(CheckinRequestTest, FetcherDataAndURL) {
+ CreateRequest(kAndroidId, kSecurityToken);
+ request_->Start();
+
+ // Get data sent by request.
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ EXPECT_EQ(GURL(kCheckinURL), fetcher->GetOriginalURL());
+
+ checkin_proto::AndroidCheckinRequest request_proto;
+ request_proto.ParseFromString(fetcher->upload_data());
+ EXPECT_EQ(kAndroidId, static_cast<uint64>(request_proto.id()));
+ EXPECT_EQ(kSecurityToken, request_proto.security_token());
+ EXPECT_EQ(chrome_build_proto_.platform(),
+ request_proto.checkin().chrome_build().platform());
+ EXPECT_EQ(chrome_build_proto_.chrome_version(),
+ request_proto.checkin().chrome_build().chrome_version());
+ EXPECT_EQ(chrome_build_proto_.channel(),
+ request_proto.checkin().chrome_build().channel());
+
+#if defined(CHROME_OS)
+ EXPECT_EQ(checkin_proto::DEVICE_CHROME_OS, request_proto.checkin().type());
+#else
+ EXPECT_EQ(checkin_proto::DEVICE_CHROME_BROWSER,
+ request_proto.checkin().type());
+#endif
+
+ EXPECT_EQ(kSettingsDigest, request_proto.digest());
+}
+
+TEST_F(CheckinRequestTest, ResponseBodyEmpty) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, std::string());
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseBodyCorrupted) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Corrupted response body");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseHttpStatusUnauthorized) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_UNAUTHORIZED, std::string());
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kBlankAndroidId, android_id_);
+ EXPECT_EQ(kBlankSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseHttpStatusBadRequest) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_BAD_REQUEST, std::string());
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kBlankAndroidId, android_id_);
+ EXPECT_EQ(kBlankSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseHttpStatusNotOK) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_INTERNAL_SERVER_ERROR, std::string());
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseMissingAndroidId) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponse(MISSING_ANDROID_ID);
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, ResponseMissingSecurityToken) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponse(MISSING_SECURITY_TOKEN);
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, AndroidIdEqualsZeroInResponse) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponse(ANDROID_ID_IS_ZER0);
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, SecurityTokenEqualsZeroInResponse) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponse(SECURITY_TOKEN_IS_ZERO);
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, SuccessfulFirstTimeCheckin) {
+ CreateRequest(0u, 0u);
+ request_->Start();
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+TEST_F(CheckinRequestTest, SuccessfulSubsequentCheckin) {
+ CreateRequest(kAndroidId, kSecurityToken);
+ request_->Start();
+
+ SetResponse(VALID_RESPONSE);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(kAndroidId, android_id_);
+ EXPECT_EQ(kSecurityToken, security_token_);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/connection_factory.cc b/chromium/google_apis/gcm/engine/connection_factory.cc
index 016e1e2b89c..a4bffd17076 100644
--- a/chromium/google_apis/gcm/engine/connection_factory.cc
+++ b/chromium/google_apis/gcm/engine/connection_factory.cc
@@ -6,6 +6,9 @@
namespace gcm {
+ConnectionFactory::ConnectionListener::ConnectionListener() {}
+ConnectionFactory::ConnectionListener::~ConnectionListener() {}
+
ConnectionFactory::ConnectionFactory() {}
ConnectionFactory::~ConnectionFactory() {}
diff --git a/chromium/google_apis/gcm/engine/connection_factory.h b/chromium/google_apis/gcm/engine/connection_factory.h
index 3cff48299b6..1db02a6ca13 100644
--- a/chromium/google_apis/gcm/engine/connection_factory.h
+++ b/chromium/google_apis/gcm/engine/connection_factory.h
@@ -5,10 +5,18 @@
#ifndef GOOGLE_APIS_GCM_ENGINE_CONNECTION_FACTORY_H_
#define GOOGLE_APIS_GCM_ENGINE_CONNECTION_FACTORY_H_
+#include <string>
+
#include "base/time/time.h"
#include "google_apis/gcm/base/gcm_export.h"
#include "google_apis/gcm/engine/connection_handler.h"
+class GURL;
+
+namespace net {
+class IPEndPoint;
+}
+
namespace mcs_proto {
class LoginRequest;
}
@@ -23,6 +31,35 @@ class GCM_EXPORT ConnectionFactory {
typedef base::Callback<void(mcs_proto::LoginRequest* login_request)>
BuildLoginRequestCallback;
+ // Reasons for triggering a connection reset. Note that these enums are
+ // consumed by a histogram, so ordering should not be modified.
+ enum ConnectionResetReason {
+ LOGIN_FAILURE, // Login response included an error.
+ CLOSE_COMMAND, // Received a close command.
+ HEARTBEAT_FAILURE, // Heartbeat was not acknowledged in time.
+ SOCKET_FAILURE, // net::Socket error.
+ NETWORK_CHANGE, // NetworkChangeNotifier notified of a network change.
+ // Count of total number of connection reset reasons. All new reset reasons
+ // should be added above this line.
+ CONNECTION_RESET_COUNT,
+ };
+
+ // Listener interface to be notified of endpoint connection events.
+ class GCM_EXPORT ConnectionListener {
+ public:
+ ConnectionListener();
+ virtual ~ConnectionListener();
+
+ // Notifies the listener that GCM has performed a handshake with and is now
+ // actively connected to |current_server|. |ip_endpoint| is the resolved
+ // ip address/port through which the connection is being made.
+ virtual void OnConnected(const GURL& current_server,
+ const net::IPEndPoint& ip_endpoint) = 0;
+
+ // Notifies the listener that the connection has been interrupted.
+ virtual void OnDisconnected() = 0;
+ };
+
ConnectionFactory();
virtual ~ConnectionFactory();
@@ -53,10 +90,24 @@ class GCM_EXPORT ConnectionFactory {
// connection.
virtual bool IsEndpointReachable() const = 0;
+ // Returns a debug string describing the connection state.
+ virtual std::string GetConnectionStateString() const = 0;
+
// If in backoff, the time at which the next retry will be made. Otherwise,
// a null time, indicating either no attempt to connect has been made or no
// backoff is in progress.
virtual base::TimeTicks NextRetryAttempt() const = 0;
+
+ // Manually reset the connection. This can occur if an application specific
+ // event forced a reset (e.g. server sends a close connection response).
+ // If the last connection was made within kConnectionResetWindowSecs, the old
+ // backoff is restored, else a new backoff kicks off.
+ virtual void SignalConnectionReset(ConnectionResetReason reason) = 0;
+
+ // Sets the current connection listener. Only one listener is supported at a
+ // time, and the listener must either outlive the connection factory or
+ // call SetConnectionListener(NULL) upon destruction.
+ virtual void SetConnectionListener(ConnectionListener* listener) = 0;
};
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/connection_factory_impl.cc b/chromium/google_apis/gcm/engine/connection_factory_impl.cc
index 388b9dca5e3..d322ffa0e94 100644
--- a/chromium/google_apis/gcm/engine/connection_factory_impl.cc
+++ b/chromium/google_apis/gcm/engine/connection_factory_impl.cc
@@ -5,7 +5,10 @@
#include "google_apis/gcm/engine/connection_factory_impl.h"
#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
#include "google_apis/gcm/engine/connection_handler_impl.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
#include "google_apis/gcm/protocol/mcs.pb.h"
#include "net/base/net_errors.h"
#include "net/http/http_network_session.h"
@@ -22,46 +25,52 @@ namespace {
// The amount of time a Socket read should wait before timing out.
const int kReadTimeoutMs = 30000; // 30 seconds.
-// Backoff policy.
-const net::BackoffEntry::Policy kConnectionBackoffPolicy = {
- // Number of initial errors (in sequence) to ignore before applying
- // exponential back-off rules.
- 0,
-
- // Initial delay for exponential back-off in ms.
- 10000, // 10 seconds.
-
- // Factor by which the waiting time will be multiplied.
- 2,
-
- // Fuzzing percentage. ex: 10% will spread requests randomly
- // between 90%-100% of the calculated time.
- 0.2, // 20%.
-
- // Maximum amount of time we are willing to delay our request in ms.
- 1000 * 3600 * 4, // 4 hours.
-
- // Time to keep an entry from being discarded even when it
- // has no significant state, -1 to never discard.
- -1,
-
- // Don't use initial delay unless the last request was an error.
- false,
-};
+// If a connection is reset after succeeding within this window of time,
+// the previous backoff entry is restored (and the connection success is treated
+// as if it was transient).
+const int kConnectionResetWindowSecs = 10; // 10 seconds.
+
+// Decides whether the last login was within kConnectionResetWindowSecs of now
+// or not.
+bool ShouldRestorePreviousBackoff(const base::TimeTicks& login_time,
+ const base::TimeTicks& now_ticks) {
+ return !login_time.is_null() &&
+ now_ticks - login_time <=
+ base::TimeDelta::FromSeconds(kConnectionResetWindowSecs);
+}
} // namespace
ConnectionFactoryImpl::ConnectionFactoryImpl(
- const GURL& mcs_endpoint,
+ const std::vector<GURL>& mcs_endpoints,
+ const net::BackoffEntry::Policy& backoff_policy,
scoped_refptr<net::HttpNetworkSession> network_session,
- net::NetLog* net_log)
- : mcs_endpoint_(mcs_endpoint),
+ net::NetLog* net_log,
+ GCMStatsRecorder* recorder)
+ : mcs_endpoints_(mcs_endpoints),
+ next_endpoint_(0),
+ last_successful_endpoint_(0),
+ backoff_policy_(backoff_policy),
network_session_(network_session),
- net_log_(net_log),
+ bound_net_log_(
+ net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)),
+ pac_request_(NULL),
+ connecting_(false),
+ waiting_for_backoff_(false),
+ waiting_for_network_online_(false),
+ logging_in_(false),
+ recorder_(recorder),
+ listener_(NULL),
weak_ptr_factory_(this) {
+ DCHECK_GE(mcs_endpoints_.size(), 1U);
}
ConnectionFactoryImpl::~ConnectionFactoryImpl() {
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+ if (pac_request_) {
+ network_session_->proxy_service()->CancelPacRequest(pac_request_);
+ pac_request_ = NULL;
+ }
}
void ConnectionFactoryImpl::Initialize(
@@ -70,18 +79,18 @@ void ConnectionFactoryImpl::Initialize(
const ConnectionHandler::ProtoSentCallback& write_callback) {
DCHECK(!connection_handler_);
- backoff_entry_ = CreateBackoffEntry(&kConnectionBackoffPolicy);
+ previous_backoff_ = CreateBackoffEntry(&backoff_policy_);
+ backoff_entry_ = CreateBackoffEntry(&backoff_policy_);
request_builder_ = request_builder;
- net::NetworkChangeNotifier::AddIPAddressObserver(this);
- net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
- connection_handler_.reset(
- new ConnectionHandlerImpl(
- base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
- read_callback,
- write_callback,
- base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
- weak_ptr_factory_.GetWeakPtr())));
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+ waiting_for_network_online_ = net::NetworkChangeNotifier::IsOffline();
+ connection_handler_ = CreateConnectionHandler(
+ base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
+ read_callback,
+ write_callback,
+ base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
+ weak_ptr_factory_.GetWeakPtr())).Pass();
}
ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
@@ -90,21 +99,41 @@ ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
void ConnectionFactoryImpl::Connect() {
DCHECK(connection_handler_);
- DCHECK(!IsEndpointReachable());
+
+ if (connecting_ || waiting_for_backoff_)
+ return; // Connection attempt already in progress or pending.
+
+ if (IsEndpointReachable())
+ return; // Already connected.
+
+ ConnectWithBackoff();
+}
+
+void ConnectionFactoryImpl::ConnectWithBackoff() {
+ // If a canary managed to connect while a backoff expiration was pending,
+ // just cleanup the internal state.
+ if (connecting_ || logging_in_ || IsEndpointReachable()) {
+ waiting_for_backoff_ = false;
+ return;
+ }
if (backoff_entry_->ShouldRejectRequest()) {
DVLOG(1) << "Delaying MCS endpoint connection for "
<< backoff_entry_->GetTimeUntilRelease().InMilliseconds()
<< " milliseconds.";
+ waiting_for_backoff_ = true;
+ recorder_->RecordConnectionDelayedDueToBackoff(
+ backoff_entry_->GetTimeUntilRelease().InMilliseconds());
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
- base::Bind(&ConnectionFactoryImpl::Connect,
+ base::Bind(&ConnectionFactoryImpl::ConnectWithBackoff,
weak_ptr_factory_.GetWeakPtr()),
- NextRetryAttempt() - base::TimeTicks::Now());
+ backoff_entry_->GetTimeUntilRelease());
return;
}
DVLOG(1) << "Attempting connection to MCS endpoint.";
+ waiting_for_backoff_ = false;
ConnectImpl();
}
@@ -112,54 +141,153 @@ bool ConnectionFactoryImpl::IsEndpointReachable() const {
return connection_handler_ && connection_handler_->CanSendMessage();
}
+std::string ConnectionFactoryImpl::GetConnectionStateString() const {
+ if (IsEndpointReachable())
+ return "CONNECTED";
+ if (logging_in_)
+ return "LOGGING IN";
+ if (connecting_)
+ return "CONNECTING";
+ if (waiting_for_backoff_)
+ return "WAITING FOR BACKOFF";
+ if (waiting_for_network_online_)
+ return "WAITING FOR NETWORK CHANGE";
+ return "NOT CONNECTED";
+}
+
+void ConnectionFactoryImpl::SignalConnectionReset(
+ ConnectionResetReason reason) {
+ // A failure can trigger multiple resets, so no need to do anything if a
+ // connection is already in progress.
+ if (connecting_) {
+ DVLOG(1) << "Connection in progress, ignoring reset.";
+ return;
+ }
+
+ if (listener_)
+ listener_->OnDisconnected();
+
+ UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason",
+ reason,
+ CONNECTION_RESET_COUNT);
+ recorder_->RecordConnectionResetSignaled(reason);
+ if (!last_login_time_.is_null()) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime",
+ NowTicks() - last_login_time_,
+ base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromHours(24),
+ 50);
+ // |last_login_time_| will be reset below, before attempting the new
+ // connection.
+ }
+
+ CloseSocket();
+ DCHECK(!IsEndpointReachable());
+
+ // TODO(zea): if the network is offline, don't attempt to connect.
+ // See crbug.com/396687
+
+ // Network changes get special treatment as they can trigger a one-off canary
+ // request that bypasses backoff (but does nothing if a connection is in
+ // progress). Other connection reset events can be ignored as a connection
+ // is already awaiting backoff expiration.
+ if (waiting_for_backoff_ && reason != NETWORK_CHANGE) {
+ DVLOG(1) << "Backoff expiration pending, ignoring reset.";
+ return;
+ }
+
+ if (logging_in_) {
+ // Failures prior to login completion just reuse the existing backoff entry.
+ logging_in_ = false;
+ backoff_entry_->InformOfRequest(false);
+ } else if (reason == LOGIN_FAILURE ||
+ ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) {
+ // Failures due to login, or within the reset window of a login, restore
+ // the backoff entry that was saved off at login completion time.
+ backoff_entry_.swap(previous_backoff_);
+ backoff_entry_->InformOfRequest(false);
+ } else if (reason == NETWORK_CHANGE) {
+ ConnectImpl(); // Canary attempts bypass backoff without resetting it.
+ return;
+ } else {
+ // We shouldn't be in backoff in thise case.
+ DCHECK_EQ(0, backoff_entry_->failure_count());
+ }
+
+ // At this point the last login time has been consumed or deemed irrelevant,
+ // reset it.
+ last_login_time_ = base::TimeTicks();
+
+ Connect();
+}
+
+void ConnectionFactoryImpl::SetConnectionListener(
+ ConnectionListener* listener) {
+ listener_ = listener;
+}
+
base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
if (!backoff_entry_)
return base::TimeTicks();
return backoff_entry_->GetReleaseTime();
}
-void ConnectionFactoryImpl::OnConnectionTypeChanged(
+void ConnectionFactoryImpl::OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType type) {
- if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
+ if (type == net::NetworkChangeNotifier::CONNECTION_NONE) {
+ DVLOG(1) << "Network lost, resettion connection.";
+ waiting_for_network_online_ = true;
+
+ // Will do nothing due to |waiting_for_network_online_ == true|.
+ // TODO(zea): make the above statement actually true. See crbug.com/396687
+ SignalConnectionReset(NETWORK_CHANGE);
return;
+ }
- // TODO(zea): implement different backoff/retry policies based on connection
- // type.
- DVLOG(1) << "Connection type changed to " << type << ", resetting backoff.";
- backoff_entry_->Reset();
- // Connect(..) should be retrying with backoff already if a connection is
- // necessary, so no need to call again.
+ DVLOG(1) << "Connection type changed to " << type << ", reconnecting.";
+ waiting_for_network_online_ = false;
+ SignalConnectionReset(NETWORK_CHANGE);
}
-void ConnectionFactoryImpl::OnIPAddressChanged() {
- DVLOG(1) << "IP Address changed, resetting backoff.";
- backoff_entry_->Reset();
- // Connect(..) should be retrying with backoff already if a connection is
- // necessary, so no need to call again.
+GURL ConnectionFactoryImpl::GetCurrentEndpoint() const {
+ // Note that IsEndpointReachable() returns false anytime connecting_ is true,
+ // so while connecting this always uses |next_endpoint_|.
+ if (IsEndpointReachable())
+ return mcs_endpoints_[last_successful_endpoint_];
+ return mcs_endpoints_[next_endpoint_];
}
-void ConnectionFactoryImpl::ConnectImpl() {
- DCHECK(!IsEndpointReachable());
+net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() {
+ if (!socket_handle_.socket())
+ return net::IPEndPoint();
- // TODO(zea): resolve proxies.
- net::ProxyInfo proxy_info;
- proxy_info.UseDirect();
- net::SSLConfig ssl_config;
- network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
+ net::IPEndPoint ip_endpoint;
+ int result = socket_handle_.socket()->GetPeerAddress(&ip_endpoint);
+ if (result != net::OK)
+ return net::IPEndPoint();
- int status = net::InitSocketHandleForTlsConnect(
- net::HostPortPair::FromURL(mcs_endpoint_),
- network_session_.get(),
- proxy_info,
- ssl_config,
- ssl_config,
- net::kPrivacyModeDisabled,
- net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_SOCKET),
- &socket_handle_,
- base::Bind(&ConnectionFactoryImpl::OnConnectDone,
- weak_ptr_factory_.GetWeakPtr()));
+ return ip_endpoint;
+}
+
+void ConnectionFactoryImpl::ConnectImpl() {
+ DCHECK(!IsEndpointReachable());
+ DCHECK(!socket_handle_.socket());
+
+ // TODO(zea): if the network is offline, don't attempt to connect.
+ // See crbug.com/396687
+
+ connecting_ = true;
+ GURL current_endpoint = GetCurrentEndpoint();
+ recorder_->RecordConnectionInitiated(current_endpoint.host());
+ int status = network_session_->proxy_service()->ResolveProxy(
+ current_endpoint,
+ &proxy_info_,
+ base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+ weak_ptr_factory_.GetWeakPtr()),
+ &pac_request_,
+ bound_net_log_);
if (status != net::ERR_IO_PENDING)
- OnConnectDone(status);
+ OnProxyResolveDone(status);
}
void ConnectionFactoryImpl::InitHandler() {
@@ -170,7 +298,7 @@ void ConnectionFactoryImpl::InitHandler() {
DCHECK(login_request.IsInitialized());
}
- connection_handler_->Init(login_request, socket_handle_.PassSocket());
+ connection_handler_->Init(login_request, socket_handle_.socket());
}
scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
@@ -178,28 +306,238 @@ scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
}
+scoped_ptr<ConnectionHandler> ConnectionFactoryImpl::CreateConnectionHandler(
+ base::TimeDelta read_timeout,
+ const ConnectionHandler::ProtoReceivedCallback& read_callback,
+ const ConnectionHandler::ProtoSentCallback& write_callback,
+ const ConnectionHandler::ConnectionChangedCallback& connection_callback) {
+ return make_scoped_ptr<ConnectionHandler>(
+ new ConnectionHandlerImpl(read_timeout,
+ read_callback,
+ write_callback,
+ connection_callback));
+}
+
+base::TimeTicks ConnectionFactoryImpl::NowTicks() {
+ return base::TimeTicks::Now();
+}
+
void ConnectionFactoryImpl::OnConnectDone(int result) {
if (result != net::OK) {
+ // If the connection fails, try another proxy.
+ result = ReconsiderProxyAfterError(result);
+ // ReconsiderProxyAfterError either returns an error (in which case it is
+ // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering
+ // another proxy.
+ DCHECK_NE(result, net::OK);
+ if (result == net::ERR_IO_PENDING)
+ return; // Proxy reconsideration pending. Return.
LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result;
+ UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", false);
+ recorder_->RecordConnectionFailure(result);
+ CloseSocket();
backoff_entry_->InformOfRequest(false);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
+
+ // If there are other endpoints available, use the next endpoint on the
+ // subsequent retry.
+ next_endpoint_++;
+ if (next_endpoint_ >= mcs_endpoints_.size())
+ next_endpoint_ = 0;
+ connecting_ = false;
Connect();
return;
}
- DVLOG(1) << "MCS endpoint connection success.";
+ UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true);
+ UMA_HISTOGRAM_COUNTS("GCM.ConnectionEndpoint", next_endpoint_);
+ UMA_HISTOGRAM_BOOLEAN("GCM.ConnectedViaProxy",
+ !(proxy_info_.is_empty() || proxy_info_.is_direct()));
+ ReportSuccessfulProxyConnection();
+ recorder_->RecordConnectionSuccess();
+
+ // Reset the endpoint back to the default.
+ // TODO(zea): consider prioritizing endpoints more intelligently based on
+ // which ones succeed most for this client? Although that will affect
+ // measuring the success rate of the default endpoint vs fallback.
+ last_successful_endpoint_ = next_endpoint_;
+ next_endpoint_ = 0;
+ connecting_ = false;
+ logging_in_ = true;
+ DVLOG(1) << "MCS endpoint socket connection success, starting login.";
+ InitHandler();
+}
- // Reset the backoff.
+void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
+ DCHECK(!connecting_);
+ if (result != net::OK) {
+ // TODO(zea): Consider how to handle errors that may require some sort of
+ // user intervention (login page, etc.).
+ UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result);
+ SignalConnectionReset(SOCKET_FAILURE);
+ return;
+ }
+
+ // Handshake complete, reset backoff. If the login failed with an error,
+ // the client should invoke SignalConnectionReset(LOGIN_FAILURE), which will
+ // restore the previous backoff.
+ DVLOG(1) << "Handshake complete.";
+ last_login_time_ = NowTicks();
+ previous_backoff_.swap(backoff_entry_);
backoff_entry_->Reset();
+ logging_in_ = false;
- InitHandler();
+ if (listener_)
+ listener_->OnConnected(GetCurrentEndpoint(), GetPeerIP());
}
-void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
- // TODO(zea): Consider how to handle errors that may require some sort of
- // user intervention (login page, etc.).
- LOG(ERROR) << "Connection reset with error " << result;
- backoff_entry_->InformOfRequest(false);
- Connect();
+// This has largely been copied from
+// HttpStreamFactoryImpl::Job::DoResolveProxyComplete. This should be
+// refactored into some common place.
+void ConnectionFactoryImpl::OnProxyResolveDone(int status) {
+ pac_request_ = NULL;
+ DVLOG(1) << "Proxy resolution status: " << status;
+
+ DCHECK_NE(status, net::ERR_IO_PENDING);
+ if (status == net::OK) {
+ // Remove unsupported proxies from the list.
+ proxy_info_.RemoveProxiesWithoutScheme(
+ net::ProxyServer::SCHEME_DIRECT |
+ net::ProxyServer::SCHEME_HTTP | net::ProxyServer::SCHEME_HTTPS |
+ net::ProxyServer::SCHEME_SOCKS4 | net::ProxyServer::SCHEME_SOCKS5);
+
+ if (proxy_info_.is_empty()) {
+ // No proxies/direct to choose from. This happens when we don't support
+ // any of the proxies in the returned list.
+ status = net::ERR_NO_SUPPORTED_PROXIES;
+ }
+ }
+
+ if (status != net::OK) {
+ // Failed to resolve proxy. Retry later.
+ OnConnectDone(status);
+ return;
+ }
+
+ DVLOG(1) << "Resolved proxy with PAC:" << proxy_info_.ToPacString();
+
+ net::SSLConfig ssl_config;
+ network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
+ status = net::InitSocketHandleForTlsConnect(
+ net::HostPortPair::FromURL(GetCurrentEndpoint()),
+ network_session_.get(),
+ proxy_info_,
+ ssl_config,
+ ssl_config,
+ net::PRIVACY_MODE_DISABLED,
+ bound_net_log_,
+ &socket_handle_,
+ base::Bind(&ConnectionFactoryImpl::OnConnectDone,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (status != net::ERR_IO_PENDING)
+ OnConnectDone(status);
+}
+
+// This has largely been copied from
+// HttpStreamFactoryImpl::Job::ReconsiderProxyAfterError. This should be
+// refactored into some common place.
+// This method reconsiders the proxy on certain errors. If it does reconsider
+// a proxy it always returns ERR_IO_PENDING and posts a call to
+// OnProxyResolveDone with the result of the reconsideration.
+int ConnectionFactoryImpl::ReconsiderProxyAfterError(int error) {
+ DCHECK(!pac_request_);
+ DCHECK_NE(error, net::OK);
+ DCHECK_NE(error, net::ERR_IO_PENDING);
+ // A failure to resolve the hostname or any error related to establishing a
+ // TCP connection could be grounds for trying a new proxy configuration.
+ //
+ // Why do this when a hostname cannot be resolved? Some URLs only make sense
+ // to proxy servers. The hostname in those URLs might fail to resolve if we
+ // are still using a non-proxy config. We need to check if a proxy config
+ // now exists that corresponds to a proxy server that could load the URL.
+ //
+ switch (error) {
+ case net::ERR_PROXY_CONNECTION_FAILED:
+ case net::ERR_NAME_NOT_RESOLVED:
+ case net::ERR_INTERNET_DISCONNECTED:
+ case net::ERR_ADDRESS_UNREACHABLE:
+ case net::ERR_CONNECTION_CLOSED:
+ case net::ERR_CONNECTION_TIMED_OUT:
+ case net::ERR_CONNECTION_RESET:
+ case net::ERR_CONNECTION_REFUSED:
+ case net::ERR_CONNECTION_ABORTED:
+ case net::ERR_TIMED_OUT:
+ case net::ERR_TUNNEL_CONNECTION_FAILED:
+ case net::ERR_SOCKS_CONNECTION_FAILED:
+ // This can happen in the case of trying to talk to a proxy using SSL, and
+ // ending up talking to a captive portal that supports SSL instead.
+ case net::ERR_PROXY_CERTIFICATE_INVALID:
+ // This can happen when trying to talk SSL to a non-SSL server (Like a
+ // captive portal).
+ case net::ERR_SSL_PROTOCOL_ERROR:
+ break;
+ case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
+ // Remap the SOCKS-specific "host unreachable" error to a more
+ // generic error code (this way consumers like the link doctor
+ // know to substitute their error page).
+ //
+ // Note that if the host resolving was done by the SOCKS5 proxy, we can't
+ // differentiate between a proxy-side "host not found" versus a proxy-side
+ // "address unreachable" error, and will report both of these failures as
+ // ERR_ADDRESS_UNREACHABLE.
+ return net::ERR_ADDRESS_UNREACHABLE;
+ default:
+ return error;
+ }
+
+ net::SSLConfig ssl_config;
+ network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
+ if (proxy_info_.is_https() && ssl_config.send_client_cert) {
+ network_session_->ssl_client_auth_cache()->Remove(
+ proxy_info_.proxy_server().host_port_pair());
+ }
+
+ int status = network_session_->proxy_service()->ReconsiderProxyAfterError(
+ GetCurrentEndpoint(), error, &proxy_info_,
+ base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+ weak_ptr_factory_.GetWeakPtr()),
+ &pac_request_,
+ bound_net_log_);
+ if (status == net::OK || status == net::ERR_IO_PENDING) {
+ CloseSocket();
+ } else {
+ // If ReconsiderProxyAfterError() failed synchronously, it means
+ // there was nothing left to fall-back to, so fail the transaction
+ // with the last connection error we got.
+ status = error;
+ }
+
+ // If there is new proxy info, post OnProxyResolveDone to retry it. Otherwise,
+ // if there was an error falling back, fail synchronously.
+ if (status == net::OK) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone,
+ weak_ptr_factory_.GetWeakPtr(), status));
+ status = net::ERR_IO_PENDING;
+ }
+ return status;
+}
+
+void ConnectionFactoryImpl::ReportSuccessfulProxyConnection() {
+ if (network_session_ && network_session_->proxy_service())
+ network_session_->proxy_service()->ReportSuccess(proxy_info_);
+}
+
+void ConnectionFactoryImpl::CloseSocket() {
+ // The connection handler needs to be reset, else it'll attempt to keep using
+ // the destroyed socket.
+ if (connection_handler_)
+ connection_handler_->Reset();
+
+ if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
+ socket_handle_.socket()->Disconnect();
+ socket_handle_.Reset();
}
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/connection_factory_impl.h b/chromium/google_apis/gcm/engine/connection_factory_impl.h
index d807270bfdc..80fbc94ea8c 100644
--- a/chromium/google_apis/gcm/engine/connection_factory_impl.h
+++ b/chromium/google_apis/gcm/engine/connection_factory_impl.h
@@ -8,9 +8,12 @@
#include "google_apis/gcm/engine/connection_factory.h"
#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
#include "google_apis/gcm/protocol/mcs.pb.h"
#include "net/base/backoff_entry.h"
#include "net/base/network_change_notifier.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_handle.h"
#include "url/gurl.h"
@@ -22,16 +25,18 @@ class NetLog;
namespace gcm {
class ConnectionHandlerImpl;
+class GCMStatsRecorder;
class GCM_EXPORT ConnectionFactoryImpl :
public ConnectionFactory,
- public net::NetworkChangeNotifier::ConnectionTypeObserver,
- public net::NetworkChangeNotifier::IPAddressObserver {
+ public net::NetworkChangeNotifier::NetworkChangeObserver {
public:
ConnectionFactoryImpl(
- const GURL& mcs_endpoint,
+ const std::vector<GURL>& mcs_endpoints,
+ const net::BackoffEntry::Policy& backoff_policy,
scoped_refptr<net::HttpNetworkSession> network_session,
- net::NetLog* net_log);
+ net::NetLog* net_log,
+ GCMStatsRecorder* recorder);
virtual ~ConnectionFactoryImpl();
// ConnectionFactory implementation.
@@ -42,12 +47,23 @@ class GCM_EXPORT ConnectionFactoryImpl :
virtual ConnectionHandler* GetConnectionHandler() const OVERRIDE;
virtual void Connect() OVERRIDE;
virtual bool IsEndpointReachable() const OVERRIDE;
+ virtual std::string GetConnectionStateString() const OVERRIDE;
virtual base::TimeTicks NextRetryAttempt() const OVERRIDE;
+ virtual void SignalConnectionReset(ConnectionResetReason reason) OVERRIDE;
+ virtual void SetConnectionListener(ConnectionListener* listener) OVERRIDE;
- // NetworkChangeNotifier observer implementations.
- virtual void OnConnectionTypeChanged(
+ // NetworkChangeObserver implementation.
+ virtual void OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType type) OVERRIDE;
- virtual void OnIPAddressChanged() OVERRIDE;
+
+ // Returns the server to which the factory is currently connected, or if
+ // a connection is currently pending, the server to which the next connection
+ // attempt will be made.
+ GURL GetCurrentEndpoint() const;
+
+ // Returns the IPEndpoint to which the factory is currently connected. If no
+ // connection is active, returns an empty IPEndpoint.
+ net::IPEndPoint GetPeerIP();
protected:
// Implementation of Connect(..). If not in backoff, uses |login_request_|
@@ -65,32 +81,98 @@ class GCM_EXPORT ConnectionFactoryImpl :
virtual scoped_ptr<net::BackoffEntry> CreateBackoffEntry(
const net::BackoffEntry::Policy* const policy);
+ // Helper method for creating the connection handler.
+ // Virtual for testing.
+ virtual scoped_ptr<ConnectionHandler> CreateConnectionHandler(
+ base::TimeDelta read_timeout,
+ const ConnectionHandler::ProtoReceivedCallback& read_callback,
+ const ConnectionHandler::ProtoSentCallback& write_callback,
+ const ConnectionHandler::ConnectionChangedCallback& connection_callback);
+
+ // Returns the current time in Ticks.
+ // Virtual for testing.
+ virtual base::TimeTicks NowTicks();
+
// Callback for Socket connection completion.
void OnConnectDone(int result);
- private:
// ConnectionHandler callback for connection issues.
void ConnectionHandlerCallback(int result);
- // The MCS endpoint to make connections to.
- const GURL mcs_endpoint_;
+ private:
+ // Helper method for checking backoff and triggering a connection as
+ // necessary.
+ void ConnectWithBackoff();
+
+ // Proxy resolution and connection functions.
+ void OnProxyResolveDone(int status);
+ void OnProxyConnectDone(int status);
+ int ReconsiderProxyAfterError(int error);
+ void ReportSuccessfulProxyConnection();
+
+ void CloseSocket();
+
+ // The MCS endpoints to make connections to, sorted in order of priority.
+ const std::vector<GURL> mcs_endpoints_;
+ // Index to the endpoint for which a connection should be attempted next.
+ size_t next_endpoint_;
+ // Index to the endpoint that was last successfully connected.
+ size_t last_successful_endpoint_;
+
+ // The backoff policy to use.
+ const net::BackoffEntry::Policy backoff_policy_;
// ---- net:: components for establishing connections. ----
// Network session for creating new connections.
const scoped_refptr<net::HttpNetworkSession> network_session_;
// Net log to use in connection attempts.
- net::NetLog* const net_log_;
+ net::BoundNetLog bound_net_log_;
+ // The current PAC request, if one exists. Owned by the proxy service.
+ net::ProxyService::PacRequest* pac_request_;
+ // The current proxy info.
+ net::ProxyInfo proxy_info_;
// The handle to the socket for the current connection, if one exists.
net::ClientSocketHandle socket_handle_;
- // Connection attempt backoff policy.
+ // Current backoff entry.
scoped_ptr<net::BackoffEntry> backoff_entry_;
+ // Backoff entry from previous connection attempt. Updated on each login
+ // completion.
+ scoped_ptr<net::BackoffEntry> previous_backoff_;
+
+ // Whether a connection attempt is currently actively in progress.
+ bool connecting_;
+
+ // Whether the client is waiting for backoff to finish before attempting to
+ // connect. Canary jobs are able to preempt connections pending backoff
+ // expiration.
+ bool waiting_for_backoff_;
+
+ // Whether the NetworkChangeNotifier has informed the client that there is
+ // no current connection. No connection attempts will be made until the
+ // client is informed of a valid connection type.
+ bool waiting_for_network_online_;
+
+ // Whether login successfully completed after the connection was established.
+ // If a connection reset happens while attempting to log in, the current
+ // backoff entry is reused (after incrementing with a new failure).
+ bool logging_in_;
+
+ // The time of the last login completion. Used for calculating whether to
+ // restore a previous backoff entry and for measuring uptime.
+ base::TimeTicks last_login_time_;
// The current connection handler, if one exists.
- scoped_ptr<ConnectionHandlerImpl> connection_handler_;
+ scoped_ptr<ConnectionHandler> connection_handler_;
// Builder for generating new login requests.
BuildLoginRequestCallback request_builder_;
+ // Recorder that records GCM activities for debugging purpose. Not owned.
+ GCMStatsRecorder* recorder_;
+
+ // Listener for connection change events.
+ ConnectionListener* listener_;
+
base::WeakPtrFactory<ConnectionFactoryImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ConnectionFactoryImpl);
diff --git a/chromium/google_apis/gcm/engine/connection_factory_impl_unittest.cc b/chromium/google_apis/gcm/engine/connection_factory_impl_unittest.cc
index 1e0ccefef26..fda42a1f93f 100644
--- a/chromium/google_apis/gcm/engine/connection_factory_impl_unittest.cc
+++ b/chromium/google_apis/gcm/engine/connection_factory_impl_unittest.cc
@@ -8,7 +8,10 @@
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
-#include "base/time/time.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "google_apis/gcm/base/mcs_util.h"
+#include "google_apis/gcm/engine/fake_connection_handler.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
#include "net/base/backoff_entry.h"
#include "net/http/http_network_session.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -19,6 +22,7 @@ namespace gcm {
namespace {
const char kMCSEndpoint[] = "http://my.server";
+const char kMCSEndpoint2[] = "http://my.alt.server";
const int kBackoffDelayMs = 1;
const int kBackoffMultiplier = 2;
@@ -50,6 +54,13 @@ const net::BackoffEntry::Policy kTestBackoffPolicy = {
false,
};
+std::vector<GURL> BuildEndpoints() {
+ std::vector<GURL> endpoints;
+ endpoints.push_back(GURL(kMCSEndpoint));
+ endpoints.push_back(GURL(kMCSEndpoint2));
+ return endpoints;
+}
+
// Helper for calculating total expected exponential backoff delay given an
// arbitrary number of failed attempts. See BackoffEntry::CalculateReleaseTime.
double CalculateBackoff(int num_attempts) {
@@ -62,15 +73,33 @@ double CalculateBackoff(int num_attempts) {
return delay;
}
-// Helper methods that should never actually be called due to real connections
-// being stubbed out.
void ReadContinuation(
scoped_ptr<google::protobuf::MessageLite> message) {
- ADD_FAILURE();
}
void WriteContinuation() {
- ADD_FAILURE();
+}
+
+class TestBackoffEntry : public net::BackoffEntry {
+ public:
+ explicit TestBackoffEntry(base::SimpleTestTickClock* tick_clock);
+ virtual ~TestBackoffEntry();
+
+ virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE;
+
+ private:
+ base::SimpleTestTickClock* tick_clock_;
+};
+
+TestBackoffEntry::TestBackoffEntry(base::SimpleTestTickClock* tick_clock)
+ : BackoffEntry(&kTestBackoffPolicy),
+ tick_clock_(tick_clock) {
+}
+
+TestBackoffEntry::~TestBackoffEntry() {}
+
+base::TimeTicks TestBackoffEntry::ImplGetTimeNow() const {
+ return tick_clock_->NowTicks();
}
// A connection factory that stubs out network requests and overrides the
@@ -80,18 +109,34 @@ class TestConnectionFactoryImpl : public ConnectionFactoryImpl {
TestConnectionFactoryImpl(const base::Closure& finished_callback);
virtual ~TestConnectionFactoryImpl();
+ void InitializeFactory();
+
// Overridden stubs.
virtual void ConnectImpl() OVERRIDE;
virtual void InitHandler() OVERRIDE;
virtual scoped_ptr<net::BackoffEntry> CreateBackoffEntry(
const net::BackoffEntry::Policy* const policy) OVERRIDE;
+ virtual scoped_ptr<ConnectionHandler> CreateConnectionHandler(
+ base::TimeDelta read_timeout,
+ const ConnectionHandler::ProtoReceivedCallback& read_callback,
+ const ConnectionHandler::ProtoSentCallback& write_callback,
+ const ConnectionHandler::ConnectionChangedCallback& connection_callback)
+ OVERRIDE;
+ virtual base::TimeTicks NowTicks() OVERRIDE;
// Helpers for verifying connection attempts are made. Connection results
// must be consumed.
void SetConnectResult(int connect_result);
void SetMultipleConnectResults(int connect_result, int num_expected_attempts);
+ // Force a login handshake to be delayed.
+ void SetDelayLogin(bool delay_login);
+
+ base::SimpleTestTickClock* tick_clock() { return &tick_clock_; }
+
private:
+ // Clock for controlling delay.
+ base::SimpleTestTickClock tick_clock_;
// The result to return on the next connect attempt.
int connect_result_;
// The number of expected connection attempts;
@@ -99,17 +144,30 @@ class TestConnectionFactoryImpl : public ConnectionFactoryImpl {
// Whether all expected connection attempts have been fulfilled since an
// expectation was last set.
bool connections_fulfilled_;
+ // Whether to delay a login handshake completion or not.
+ bool delay_login_;
// Callback to invoke when all connection attempts have been made.
base::Closure finished_callback_;
+ // The current fake connection handler..
+ FakeConnectionHandler* fake_handler_;
+ FakeGCMStatsRecorder dummy_recorder_;
};
TestConnectionFactoryImpl::TestConnectionFactoryImpl(
const base::Closure& finished_callback)
- : ConnectionFactoryImpl(GURL(kMCSEndpoint), NULL, NULL),
- connect_result_(net::ERR_UNEXPECTED),
- num_expected_attempts_(0),
- connections_fulfilled_(true),
- finished_callback_(finished_callback) {
+ : ConnectionFactoryImpl(BuildEndpoints(),
+ net::BackoffEntry::Policy(),
+ NULL,
+ NULL,
+ &dummy_recorder_),
+ connect_result_(net::ERR_UNEXPECTED),
+ num_expected_attempts_(0),
+ connections_fulfilled_(true),
+ delay_login_(false),
+ finished_callback_(finished_callback),
+ fake_handler_(NULL) {
+ // Set a non-null time.
+ tick_clock_.Advance(base::TimeDelta::FromMilliseconds(1));
}
TestConnectionFactoryImpl::~TestConnectionFactoryImpl() {
@@ -118,8 +176,15 @@ TestConnectionFactoryImpl::~TestConnectionFactoryImpl() {
void TestConnectionFactoryImpl::ConnectImpl() {
ASSERT_GT(num_expected_attempts_, 0);
-
+ scoped_ptr<mcs_proto::LoginRequest> request(BuildLoginRequest(0, 0, ""));
+ GetConnectionHandler()->Init(*request, NULL);
OnConnectDone(connect_result_);
+ if (!NextRetryAttempt().is_null()) {
+ // Advance the time to the next retry time.
+ base::TimeDelta time_till_retry =
+ NextRetryAttempt() - tick_clock_.NowTicks();
+ tick_clock_.Advance(time_till_retry);
+ }
--num_expected_attempts_;
if (num_expected_attempts_ == 0) {
connect_result_ = net::ERR_UNEXPECTED;
@@ -130,12 +195,29 @@ void TestConnectionFactoryImpl::ConnectImpl() {
void TestConnectionFactoryImpl::InitHandler() {
EXPECT_NE(connect_result_, net::ERR_UNEXPECTED);
+ if (!delay_login_)
+ ConnectionHandlerCallback(net::OK);
}
scoped_ptr<net::BackoffEntry> TestConnectionFactoryImpl::CreateBackoffEntry(
const net::BackoffEntry::Policy* const policy) {
- return scoped_ptr<net::BackoffEntry>(
- new net::BackoffEntry(&kTestBackoffPolicy));
+ return scoped_ptr<net::BackoffEntry>(new TestBackoffEntry(&tick_clock_));
+}
+
+scoped_ptr<ConnectionHandler>
+TestConnectionFactoryImpl::CreateConnectionHandler(
+ base::TimeDelta read_timeout,
+ const ConnectionHandler::ProtoReceivedCallback& read_callback,
+ const ConnectionHandler::ProtoSentCallback& write_callback,
+ const ConnectionHandler::ConnectionChangedCallback& connection_callback) {
+ fake_handler_ = new FakeConnectionHandler(
+ base::Bind(&ReadContinuation),
+ base::Bind(&WriteContinuation));
+ return make_scoped_ptr<ConnectionHandler>(fake_handler_);
+}
+
+base::TimeTicks TestConnectionFactoryImpl::NowTicks() {
+ return tick_clock_.NowTicks();
}
void TestConnectionFactoryImpl::SetConnectResult(int connect_result) {
@@ -144,6 +226,10 @@ void TestConnectionFactoryImpl::SetConnectResult(int connect_result) {
connections_fulfilled_ = false;
connect_result_ = connect_result;
num_expected_attempts_ = 1;
+ fake_handler_->ExpectOutgoingMessage(
+ MCSMessage(kLoginRequestTag,
+ BuildLoginRequest(0, 0, "").PassAs<
+ const google::protobuf::MessageLite>()));
}
void TestConnectionFactoryImpl::SetMultipleConnectResults(
@@ -155,29 +241,58 @@ void TestConnectionFactoryImpl::SetMultipleConnectResults(
connections_fulfilled_ = false;
connect_result_ = connect_result;
num_expected_attempts_ = num_expected_attempts;
+ for (int i = 0 ; i < num_expected_attempts; ++i) {
+ fake_handler_->ExpectOutgoingMessage(
+ MCSMessage(kLoginRequestTag,
+ BuildLoginRequest(0, 0, "").PassAs<
+ const google::protobuf::MessageLite>()));
+ }
+}
+
+void TestConnectionFactoryImpl::SetDelayLogin(bool delay_login) {
+ delay_login_ = delay_login;
+ fake_handler_->set_fail_login(delay_login_);
}
-class ConnectionFactoryImplTest : public testing::Test {
+} // namespace
+
+class ConnectionFactoryImplTest
+ : public testing::Test,
+ public ConnectionFactory::ConnectionListener {
public:
ConnectionFactoryImplTest();
virtual ~ConnectionFactoryImplTest();
TestConnectionFactoryImpl* factory() { return &factory_; }
+ GURL& connected_server() { return connected_server_; }
void WaitForConnections();
+ // ConnectionFactory::ConnectionListener
+ virtual void OnConnected(const GURL& current_server,
+ const net::IPEndPoint& ip_endpoint) OVERRIDE;
+ virtual void OnDisconnected() OVERRIDE;
+
private:
void ConnectionsComplete();
TestConnectionFactoryImpl factory_;
base::MessageLoop message_loop_;
scoped_ptr<base::RunLoop> run_loop_;
+
+ GURL connected_server_;
};
ConnectionFactoryImplTest::ConnectionFactoryImplTest()
: factory_(base::Bind(&ConnectionFactoryImplTest::ConnectionsComplete,
base::Unretained(this))),
- run_loop_(new base::RunLoop()) {}
+ run_loop_(new base::RunLoop()) {
+ factory()->SetConnectionListener(this);
+ factory()->Initialize(
+ ConnectionFactory::BuildLoginRequestCallback(),
+ ConnectionHandler::ProtoReceivedCallback(),
+ ConnectionHandler::ProtoSentCallback());
+}
ConnectionFactoryImplTest::~ConnectionFactoryImplTest() {}
void ConnectionFactoryImplTest::WaitForConnections() {
@@ -191,73 +306,75 @@ void ConnectionFactoryImplTest::ConnectionsComplete() {
run_loop_->Quit();
}
+void ConnectionFactoryImplTest::OnConnected(
+ const GURL& current_server,
+ const net::IPEndPoint& ip_endpoint) {
+ connected_server_ = current_server;
+}
+
+void ConnectionFactoryImplTest::OnDisconnected() {
+ connected_server_ = GURL();
+}
+
// Verify building a connection handler works.
TEST_F(ConnectionFactoryImplTest, Initialize) {
- EXPECT_FALSE(factory()->IsEndpointReachable());
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- base::Bind(&ReadContinuation),
- base::Bind(&WriteContinuation));
ConnectionHandler* handler = factory()->GetConnectionHandler();
ASSERT_TRUE(handler);
EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
}
// An initial successful connection should not result in backoff.
TEST_F(ConnectionFactoryImplTest, ConnectSuccess) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::OK);
factory()->Connect();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+ EXPECT_EQ(factory()->GetCurrentEndpoint(), BuildEndpoints()[0]);
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
}
-// A connection failure should result in backoff.
+// A connection failure should result in backoff, and attempting the fallback
+// endpoint next.
TEST_F(ConnectionFactoryImplTest, ConnectFail) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
factory()->Connect();
EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
+ EXPECT_EQ(factory()->GetCurrentEndpoint(), BuildEndpoints()[1]);
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
}
// A connection success after a failure should reset backoff.
TEST_F(ConnectionFactoryImplTest, FailThenSucceed) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
- base::TimeTicks connect_time = base::TimeTicks::Now();
+ base::TimeTicks connect_time = factory()->tick_clock()->NowTicks();
factory()->Connect();
WaitForConnections();
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
base::TimeTicks retry_time = factory()->NextRetryAttempt();
EXPECT_FALSE(retry_time.is_null());
EXPECT_GE((retry_time - connect_time).InMilliseconds(), CalculateBackoff(1));
factory()->SetConnectResult(net::OK);
WaitForConnections();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
}
// Multiple connection failures should retry with an exponentially increasing
// backoff, then reset on success.
TEST_F(ConnectionFactoryImplTest, MultipleFailuresThenSucceed) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
-
const int kNumAttempts = 5;
factory()->SetMultipleConnectResults(net::ERR_CONNECTION_FAILED,
kNumAttempts);
- base::TimeTicks connect_time = base::TimeTicks::Now();
+ base::TimeTicks connect_time = factory()->tick_clock()->NowTicks();
factory()->Connect();
WaitForConnections();
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
base::TimeTicks retry_time = factory()->NextRetryAttempt();
EXPECT_FALSE(retry_time.is_null());
EXPECT_GE((retry_time - connect_time).InMilliseconds(),
@@ -266,38 +383,171 @@ TEST_F(ConnectionFactoryImplTest, MultipleFailuresThenSucceed) {
factory()->SetConnectResult(net::OK);
WaitForConnections();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
}
-// IP events should reset backoff.
-TEST_F(ConnectionFactoryImplTest, FailThenIPEvent) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
+// Network change events should trigger canary connections.
+TEST_F(ConnectionFactoryImplTest, FailThenNetworkChangeEvent) {
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
factory()->Connect();
WaitForConnections();
+ base::TimeTicks initial_backoff = factory()->NextRetryAttempt();
+ EXPECT_FALSE(initial_backoff.is_null());
+
+ factory()->SetConnectResult(net::ERR_FAILED);
+ factory()->OnNetworkChanged(net::NetworkChangeNotifier::CONNECTION_WIFI);
+ WaitForConnections();
+
+ // Backoff should increase.
+ base::TimeTicks next_backoff = factory()->NextRetryAttempt();
+ EXPECT_GT(next_backoff, initial_backoff);
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+}
+
+// Verify that we reconnect even if a canary succeeded then disconnected while
+// a backoff was pending.
+TEST_F(ConnectionFactoryImplTest, CanarySucceedsThenDisconnects) {
+ factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
+ factory()->Connect();
+ WaitForConnections();
+ base::TimeTicks initial_backoff = factory()->NextRetryAttempt();
+ EXPECT_FALSE(initial_backoff.is_null());
+
+ factory()->SetConnectResult(net::OK);
+ factory()->OnNetworkChanged(net::NetworkChangeNotifier::CONNECTION_ETHERNET);
+ WaitForConnections();
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
+
+ factory()->SetConnectResult(net::OK);
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
+ WaitForConnections();
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
+}
+
+// Verify that if a canary connects, but hasn't finished the handshake, a
+// pending backoff attempt doesn't interrupt the connection.
+TEST_F(ConnectionFactoryImplTest, CanarySucceedsRetryDuringLogin) {
+ factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
+ factory()->Connect();
+ WaitForConnections();
+ base::TimeTicks initial_backoff = factory()->NextRetryAttempt();
+ EXPECT_FALSE(initial_backoff.is_null());
+
+ factory()->SetDelayLogin(true);
+ factory()->SetConnectResult(net::OK);
+ factory()->OnNetworkChanged(net::NetworkChangeNotifier::CONNECTION_WIFI);
+ WaitForConnections();
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+
+ // Pump the loop, to ensure the pending backoff retry has no effect.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromMilliseconds(1));
+ WaitForConnections();
+}
+
+// Fail after successful connection via signal reset.
+TEST_F(ConnectionFactoryImplTest, FailViaSignalReset) {
+ factory()->SetConnectResult(net::OK);
+ factory()->Connect();
+ EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+}
- factory()->OnIPAddressChanged();
+TEST_F(ConnectionFactoryImplTest, IgnoreResetWhileConnecting) {
+ factory()->SetConnectResult(net::OK);
+ factory()->Connect();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
+ base::TimeTicks retry_time = factory()->NextRetryAttempt();
+ EXPECT_FALSE(retry_time.is_null());
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+
+ const int kNumAttempts = 5;
+ for (int i = 0; i < kNumAttempts; ++i)
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
+ EXPECT_EQ(retry_time, factory()->NextRetryAttempt());
+ EXPECT_FALSE(factory()->IsEndpointReachable());
}
-// Connection type events should reset backoff.
-TEST_F(ConnectionFactoryImplTest, FailThenConnectionTypeEvent) {
- factory()->Initialize(
- ConnectionFactory::BuildLoginRequestCallback(),
- ConnectionHandler::ProtoReceivedCallback(),
- ConnectionHandler::ProtoSentCallback());
+// Go into backoff due to connection failure. On successful connection, receive
+// a signal reset. The original backoff should be restored and extended, rather
+// than a new backoff starting from scratch.
+TEST_F(ConnectionFactoryImplTest, SignalResetRestoresBackoff) {
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
+ base::TimeTicks connect_time = factory()->tick_clock()->NowTicks();
factory()->Connect();
WaitForConnections();
- EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
+ base::TimeTicks retry_time = factory()->NextRetryAttempt();
+ EXPECT_FALSE(retry_time.is_null());
- factory()->OnConnectionTypeChanged(
- net::NetworkChangeNotifier::CONNECTION_WIFI);
+ factory()->SetConnectResult(net::OK);
+ connect_time = factory()->tick_clock()->NowTicks();
+ WaitForConnections();
+ EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
+ EXPECT_NE(retry_time, factory()->NextRetryAttempt());
+ retry_time = factory()->NextRetryAttempt();
+ EXPECT_FALSE(retry_time.is_null());
+ EXPECT_GE((retry_time - connect_time).InMilliseconds(),
+ CalculateBackoff(2));
+
+ factory()->SetConnectResult(net::OK);
+ connect_time = factory()->tick_clock()->NowTicks();
+ factory()->tick_clock()->Advance(
+ factory()->NextRetryAttempt() - connect_time);
+ WaitForConnections();
+ EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(connected_server().is_valid());
+
+ factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE);
+ EXPECT_NE(retry_time, factory()->NextRetryAttempt());
+ retry_time = factory()->NextRetryAttempt();
+ EXPECT_FALSE(retry_time.is_null());
+ EXPECT_GE((retry_time - connect_time).InMilliseconds(),
+ CalculateBackoff(3));
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_FALSE(connected_server().is_valid());
+}
+
+// When the network is disconnected, close the socket and suppress further
+// connection attempts until the network returns.
+// Disabled while crbug.com/396687 is being investigated.
+TEST_F(ConnectionFactoryImplTest, DISABLED_SuppressConnectWhenNoNetwork) {
+ factory()->SetConnectResult(net::OK);
+ factory()->Connect();
+ EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+ EXPECT_TRUE(factory()->IsEndpointReachable());
+
+ // Advance clock so the login window reset isn't encountered.
+ factory()->tick_clock()->Advance(base::TimeDelta::FromSeconds(11));
+
+ // Will trigger reset, but will not attempt a new connection.
+ factory()->OnNetworkChanged(net::NetworkChangeNotifier::CONNECTION_NONE);
+ EXPECT_FALSE(factory()->IsEndpointReachable());
+ EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
+
+ // When the network returns, attempt to connect.
+ factory()->SetConnectResult(net::OK);
+ factory()->OnNetworkChanged(net::NetworkChangeNotifier::CONNECTION_4G);
+ WaitForConnections();
+
+ EXPECT_TRUE(factory()->IsEndpointReachable());
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
-} // namespace
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/connection_handler.h b/chromium/google_apis/gcm/engine/connection_handler.h
index 5b9ea715c78..7b265f9d3bc 100644
--- a/chromium/google_apis/gcm/engine/connection_handler.h
+++ b/chromium/google_apis/gcm/engine/connection_handler.h
@@ -48,7 +48,11 @@ class GCM_EXPORT ConnectionHandler {
// Note: It is correct and expected to call Init more than once, as connection
// issues are encountered and new connections must be made.
virtual void Init(const mcs_proto::LoginRequest& login_request,
- scoped_ptr<net::StreamSocket> socket) = 0;
+ net::StreamSocket* socket) = 0;
+
+ // Resets the handler and any internal state. Should be called any time
+ // a connection reset happens externally to the handler.
+ virtual void Reset() = 0;
// Checks that a handshake has been completed and a message is not already
// in flight.
diff --git a/chromium/google_apis/gcm/engine/connection_handler_impl.cc b/chromium/google_apis/gcm/engine/connection_handler_impl.cc
index aff0dfd3651..d47e0f25b79 100644
--- a/chromium/google_apis/gcm/engine/connection_handler_impl.cc
+++ b/chromium/google_apis/gcm/engine/connection_handler_impl.cc
@@ -27,8 +27,7 @@ const int kSizePacketLenMin = 1;
const int kSizePacketLenMax = 2;
// The current MCS protocol version.
-// TODO(zea): bump to 41 once the server supports it.
-const int kMCSVersion = 38;
+const int kMCSVersion = 41;
} // namespace
@@ -38,6 +37,7 @@ ConnectionHandlerImpl::ConnectionHandlerImpl(
const ProtoSentCallback& write_callback,
const ConnectionChangedCallback& connection_callback)
: read_timeout_(read_timeout),
+ socket_(NULL),
handshake_complete_(false),
message_tag_(0),
message_size_(0),
@@ -52,7 +52,7 @@ ConnectionHandlerImpl::~ConnectionHandlerImpl() {
void ConnectionHandlerImpl::Init(
const mcs_proto::LoginRequest& login_request,
- scoped_ptr<net::StreamSocket> socket) {
+ net::StreamSocket* socket) {
DCHECK(!read_callback_.is_null());
DCHECK(!write_callback_.is_null());
DCHECK(!connection_callback_.is_null());
@@ -63,13 +63,17 @@ void ConnectionHandlerImpl::Init(
handshake_complete_ = false;
message_tag_ = 0;
message_size_ = 0;
- socket_ = socket.Pass();
- input_stream_.reset(new SocketInputStream(socket_.get()));
- output_stream_.reset(new SocketOutputStream(socket_.get()));
+ socket_ = socket;
+ input_stream_.reset(new SocketInputStream(socket_));
+ output_stream_.reset(new SocketOutputStream(socket_));
Login(login_request);
}
+void ConnectionHandlerImpl::Reset() {
+ CloseConnection();
+}
+
bool ConnectionHandlerImpl::CanSendMessage() const {
return handshake_complete_ && output_stream_.get() &&
output_stream_->GetState() == SocketOutputStream::EMPTY;
@@ -180,9 +184,9 @@ void ConnectionHandlerImpl::WaitForData(ProcessingState state) {
}
// Used to determine whether a Socket::Read is necessary.
- int min_bytes_needed = 0;
+ size_t min_bytes_needed = 0;
// Used to limit the size of the Socket::Read.
- int max_bytes_needed = 0;
+ size_t max_bytes_needed = 0;
switch(state) {
case MCS_VERSION_TAG_AND_SIZE:
@@ -210,20 +214,20 @@ void ConnectionHandlerImpl::WaitForData(ProcessingState state) {
}
DCHECK_GE(max_bytes_needed, min_bytes_needed);
- int byte_count = input_stream_->UnreadByteCount();
- if (min_bytes_needed - byte_count > 0 &&
+ size_t unread_byte_count = input_stream_->UnreadByteCount();
+ if (min_bytes_needed > unread_byte_count &&
input_stream_->Refresh(
base::Bind(&ConnectionHandlerImpl::WaitForData,
weak_ptr_factory_.GetWeakPtr(),
state),
- max_bytes_needed - byte_count) == net::ERR_IO_PENDING) {
+ max_bytes_needed - unread_byte_count) == net::ERR_IO_PENDING) {
return;
}
// Check for refresh errors.
if (input_stream_->GetState() != SocketInputStream::READY) {
// An error occurred.
- int last_error = output_stream_->last_error();
+ int last_error = input_stream_->last_error();
CloseConnection();
// If the socket stream had an error, plumb it up, else plumb up FAILED.
if (last_error == net::OK)
@@ -232,6 +236,20 @@ void ConnectionHandlerImpl::WaitForData(ProcessingState state) {
return;
}
+ // Check whether read is complete, or needs to be continued (
+ // SocketInputStream::Refresh can finish without reading all the data).
+ if (input_stream_->UnreadByteCount() < min_bytes_needed) {
+ DVLOG(1) << "Socket read finished prematurely. Waiting for "
+ << min_bytes_needed - input_stream_->UnreadByteCount()
+ << " more bytes.";
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ConnectionHandlerImpl::WaitForData,
+ weak_ptr_factory_.GetWeakPtr(),
+ MCS_PROTO_BYTES));
+ return;
+ }
+
// Received enough bytes, process them.
DVLOG(1) << "Processing MCS data: state == " << state;
switch(state) {
@@ -258,7 +276,8 @@ void ConnectionHandlerImpl::OnGotVersion() {
CodedInputStream coded_input_stream(input_stream_.get());
coded_input_stream.ReadRaw(&version, 1);
}
- if (version < kMCSVersion) {
+ // TODO(zea): remove this when the server is ready.
+ if (version < kMCSVersion && version != 38) {
LOG(ERROR) << "Invalid GCM version response: " << static_cast<int>(version);
connection_callback_.Run(net::ERR_FAILED);
return;
@@ -352,18 +371,18 @@ void ConnectionHandlerImpl::OnGotMessageBytes() {
input_stream_->GetState() != SocketInputStream::READY) {
LOG(ERROR) << "Failed to extract protobuf bytes of type "
<< static_cast<unsigned int>(message_tag_);
- protobuf.reset(); // Return a null pointer to denote an error.
- read_callback_.Run(protobuf.Pass());
+ // Reset the connection.
+ connection_callback_.Run(net::ERR_FAILED);
return;
}
{
CodedInputStream coded_input_stream(input_stream_.get());
if (!protobuf->ParsePartialFromCodedStream(&coded_input_stream)) {
- NOTREACHED() << "Unable to parse GCM message of type "
- << static_cast<unsigned int>(message_tag_);
- protobuf.reset(); // Return a null pointer to denote an error.
- read_callback_.Run(protobuf.Pass());
+ LOG(ERROR) << "Unable to parse GCM message of type "
+ << static_cast<unsigned int>(message_tag_);
+ // Reset the connection.
+ connection_callback_.Run(net::ERR_FAILED);
return;
}
}
@@ -379,6 +398,7 @@ void ConnectionHandlerImpl::OnGotMessageBytes() {
} else {
handshake_complete_ = true;
DVLOG(1) << "GCM Handshake complete.";
+ connection_callback_.Run(net::OK);
}
}
read_callback_.Run(protobuf.Pass());
@@ -392,10 +412,13 @@ void ConnectionHandlerImpl::OnTimeout() {
void ConnectionHandlerImpl::CloseConnection() {
DVLOG(1) << "Closing connection.";
- read_callback_.Reset();
- write_callback_.Reset();
read_timeout_timer_.Stop();
- socket_->Disconnect();
+ if (socket_)
+ socket_->Disconnect();
+ socket_ = NULL;
+ handshake_complete_ = false;
+ message_tag_ = 0;
+ message_size_ = 0;
input_stream_.reset();
output_stream_.reset();
weak_ptr_factory_.InvalidateWeakPtrs();
diff --git a/chromium/google_apis/gcm/engine/connection_handler_impl.h b/chromium/google_apis/gcm/engine/connection_handler_impl.h
index 110cdcdddda..9f9daa94e39 100644
--- a/chromium/google_apis/gcm/engine/connection_handler_impl.h
+++ b/chromium/google_apis/gcm/engine/connection_handler_impl.h
@@ -34,7 +34,8 @@ class GCM_EXPORT ConnectionHandlerImpl : public ConnectionHandler {
// ConnectionHandler implementation.
virtual void Init(const mcs_proto::LoginRequest& login_request,
- scoped_ptr<net::StreamSocket> socket) OVERRIDE;
+ net::StreamSocket* socket) OVERRIDE;
+ virtual void Reset() OVERRIDE;
virtual bool CanSendMessage() const OVERRIDE;
virtual void SendMessage(const google::protobuf::MessageLite& message)
OVERRIDE;
@@ -96,7 +97,7 @@ class GCM_EXPORT ConnectionHandlerImpl : public ConnectionHandler {
base::OneShotTimer<ConnectionHandlerImpl> read_timeout_timer_;
// This connection's socket and the input/output streams attached to it.
- scoped_ptr<net::StreamSocket> socket_;
+ net::StreamSocket* socket_;
scoped_ptr<SocketInputStream> input_stream_;
scoped_ptr<SocketOutputStream> output_stream_;
diff --git a/chromium/google_apis/gcm/engine/connection_handler_impl_unittest.cc b/chromium/google_apis/gcm/engine/connection_handler_impl_unittest.cc
index 0cdcdc621ff..6b89644462c 100644
--- a/chromium/google_apis/gcm/engine/connection_handler_impl_unittest.cc
+++ b/chromium/google_apis/gcm/engine/connection_handler_impl_unittest.cc
@@ -27,7 +27,7 @@ typedef std::vector<net::MockWrite> WriteList;
const uint64 kAuthId = 54321;
const uint64 kAuthToken = 12345;
-const char kMCSVersion = 38; // The protocol version.
+const char kMCSVersion = 41; // The protocol version.
const int kMCSPort = 5228; // The server port.
const char kDataMsgFrom[] = "data_from";
const char kDataMsgCategory[] = "data_category";
@@ -49,12 +49,16 @@ const char kDataMsgCategoryLong2[] =
std::string EncodePacket(uint8 tag, const std::string& proto) {
std::string result;
google::protobuf::io::StringOutputStream string_output_stream(&result);
- google::protobuf::io::CodedOutputStream coded_output_stream(
+ {
+ google::protobuf::io::CodedOutputStream coded_output_stream(
&string_output_stream);
- const unsigned char tag_byte[1] = {tag};
- coded_output_stream.WriteRaw(tag_byte, 1);
- coded_output_stream.WriteVarint32(proto.size());
- coded_output_stream.WriteRaw(proto.c_str(), proto.size());
+ const unsigned char tag_byte[1] = { tag };
+ coded_output_stream.WriteRaw(tag_byte, 1);
+ coded_output_stream.WriteVarint32(proto.size());
+ coded_output_stream.WriteRaw(proto.c_str(), proto.size());
+ // ~CodedOutputStream must run before the move constructor at the
+ // return statement. http://crbug.com/338962
+ }
return result;
}
@@ -63,7 +67,8 @@ std::string EncodeHandshakeRequest() {
std::string result;
const char version_byte[1] = {kMCSVersion};
result.append(version_byte, 1);
- ScopedMessage login_request(BuildLoginRequest(kAuthId, kAuthToken));
+ ScopedMessage login_request(
+ BuildLoginRequest(kAuthId, kAuthToken, ""));
result.append(EncodePacket(kLoginRequestTag,
login_request->SerializeAsString()));
return result;
@@ -198,8 +203,9 @@ void GCMConnectionHandlerImplTest::Connect(
base::Bind(&GCMConnectionHandlerImplTest::ConnectionContinuation,
base::Unretained(this))));
EXPECT_FALSE(connection_handler()->CanSendMessage());
- connection_handler_->Init(*BuildLoginRequest(kAuthId, kAuthToken),
- socket_.Pass());
+ connection_handler_->Init(
+ *BuildLoginRequest(kAuthId, kAuthToken, ""),
+ socket_.get());
}
void GCMConnectionHandlerImplTest::ReadContinuation(
@@ -376,6 +382,7 @@ TEST_F(GCMConnectionHandlerImplTest, RecvMsg) {
WaitForMessage(); // The data message.
ASSERT_TRUE(received_message.get());
EXPECT_EQ(data_message_proto, received_message->SerializeAsString());
+ EXPECT_EQ(net::OK, last_error());
}
// Verify that if two messages arrive at once, they're treated appropriately.
@@ -413,6 +420,7 @@ TEST_F(GCMConnectionHandlerImplTest, Recv2Msgs) {
WaitForMessage(); // The second data message.
ASSERT_TRUE(received_message.get());
EXPECT_EQ(data_message_proto2, received_message->SerializeAsString());
+ EXPECT_EQ(net::OK, last_error());
}
// Receive a long (>128 bytes) message.
@@ -444,6 +452,46 @@ TEST_F(GCMConnectionHandlerImplTest, RecvLongMsg) {
WaitForMessage(); // The data message.
ASSERT_TRUE(received_message.get());
EXPECT_EQ(data_message_proto, received_message->SerializeAsString());
+ EXPECT_EQ(net::OK, last_error());
+}
+
+// Receive a long (>128 bytes) message in two synchronous parts.
+TEST_F(GCMConnectionHandlerImplTest, RecvLongMsg2Parts) {
+ std::string handshake_request = EncodeHandshakeRequest();
+ WriteList write_list(1, net::MockWrite(net::ASYNC,
+ handshake_request.c_str(),
+ handshake_request.size()));
+ std::string handshake_response = EncodeHandshakeResponse();
+
+ std::string data_message_proto =
+ BuildDataMessage(kDataMsgFromLong, kDataMsgCategoryLong);
+ std::string data_message_pkt =
+ EncodePacket(kDataMessageStanzaTag, data_message_proto);
+ DCHECK_GT(data_message_pkt.size(), 128U);
+ ReadList read_list;
+ read_list.push_back(net::MockRead(net::ASYNC,
+ handshake_response.c_str(),
+ handshake_response.size()));
+
+ int bytes_in_first_message = data_message_pkt.size() / 2;
+ read_list.push_back(net::MockRead(net::SYNCHRONOUS,
+ data_message_pkt.c_str(),
+ bytes_in_first_message));
+ read_list.push_back(net::MockRead(net::SYNCHRONOUS,
+ data_message_pkt.c_str() +
+ bytes_in_first_message,
+ data_message_pkt.size() -
+ bytes_in_first_message));
+ BuildSocket(read_list, write_list);
+
+ ScopedMessage received_message;
+ Connect(&received_message);
+ WaitForMessage(); // The login send.
+ WaitForMessage(); // The login response.
+ WaitForMessage(); // The data message.
+ ASSERT_TRUE(received_message.get());
+ EXPECT_EQ(net::OK, last_error());
+ EXPECT_EQ(data_message_proto, received_message->SerializeAsString());
}
// Receive two long (>128 bytes) message.
@@ -482,6 +530,7 @@ TEST_F(GCMConnectionHandlerImplTest, Recv2LongMsgs) {
WaitForMessage(); // The second data message.
ASSERT_TRUE(received_message.get());
EXPECT_EQ(data_message_proto2, received_message->SerializeAsString());
+ EXPECT_EQ(net::OK, last_error());
}
// Simulate a message where the end of the data does not arrive in time and the
@@ -624,5 +673,37 @@ TEST_F(GCMConnectionHandlerImplTest, SendMsgSocketDisconnected) {
EXPECT_EQ(net::ERR_CONNECTION_CLOSED, last_error());
}
+// Receive a message whose size field was corrupted and is larger than the
+// socket's buffer. Should fail gracefully.
+TEST_F(GCMConnectionHandlerImplTest, CorruptedSize) {
+ std::string handshake_request = EncodeHandshakeRequest();
+ WriteList write_list(1, net::MockWrite(net::ASYNC,
+ handshake_request.c_str(),
+ handshake_request.size()));
+ std::string handshake_response = EncodeHandshakeResponse();
+
+ // Fill a string with 9000 character zero.
+ std::string data_message_proto(9000, '0');
+ std::string data_message_pkt =
+ EncodePacket(kDataMessageStanzaTag, data_message_proto);
+ ReadList read_list;
+ read_list.push_back(net::MockRead(net::ASYNC,
+ handshake_response.c_str(),
+ handshake_response.size()));
+ read_list.push_back(net::MockRead(net::ASYNC,
+ data_message_pkt.c_str(),
+ data_message_pkt.size()));
+ BuildSocket(read_list, write_list);
+
+ ScopedMessage received_message;
+ Connect(&received_message);
+ WaitForMessage(); // The login send.
+ WaitForMessage(); // The login response.
+ received_message.reset();
+ WaitForMessage(); // The data message.
+ EXPECT_FALSE(received_message.get());
+ EXPECT_EQ(net::ERR_FILE_TOO_BIG, last_error());
+}
+
} // namespace
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/fake_connection_factory.cc b/chromium/google_apis/gcm/engine/fake_connection_factory.cc
index 54b3423b2d5..51447b9e5a8 100644
--- a/chromium/google_apis/gcm/engine/fake_connection_factory.cc
+++ b/chromium/google_apis/gcm/engine/fake_connection_factory.cc
@@ -10,7 +10,9 @@
namespace gcm {
-FakeConnectionFactory::FakeConnectionFactory() {
+FakeConnectionFactory::FakeConnectionFactory()
+ : reconnect_pending_(false),
+ delay_reconnect_(false) {
}
FakeConnectionFactory::~FakeConnectionFactory() {
@@ -32,15 +34,31 @@ ConnectionHandler* FakeConnectionFactory::GetConnectionHandler() const {
void FakeConnectionFactory::Connect() {
mcs_proto::LoginRequest login_request;
request_builder_.Run(&login_request);
- connection_handler_->Init(login_request, scoped_ptr<net::StreamSocket>());
+ connection_handler_->Init(login_request, NULL);
}
bool FakeConnectionFactory::IsEndpointReachable() const {
return connection_handler_.get() && connection_handler_->CanSendMessage();
}
+std::string FakeConnectionFactory::GetConnectionStateString() const {
+ return "";
+}
+
base::TimeTicks FakeConnectionFactory::NextRetryAttempt() const {
return base::TimeTicks();
}
+void FakeConnectionFactory::SignalConnectionReset(
+ ConnectionResetReason reason) {
+ if (!delay_reconnect_)
+ Connect();
+ else
+ reconnect_pending_ = true;
+}
+
+void FakeConnectionFactory::SetConnectionListener(
+ ConnectionListener* listener) {
+}
+
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/fake_connection_factory.h b/chromium/google_apis/gcm/engine/fake_connection_factory.h
index 60b10e130db..b4f0e884d5d 100644
--- a/chromium/google_apis/gcm/engine/fake_connection_factory.h
+++ b/chromium/google_apis/gcm/engine/fake_connection_factory.h
@@ -27,13 +27,29 @@ class FakeConnectionFactory : public ConnectionFactory {
virtual ConnectionHandler* GetConnectionHandler() const OVERRIDE;
virtual void Connect() OVERRIDE;
virtual bool IsEndpointReachable() const OVERRIDE;
+ virtual std::string GetConnectionStateString() const OVERRIDE;
virtual base::TimeTicks NextRetryAttempt() const OVERRIDE;
+ virtual void SignalConnectionReset(ConnectionResetReason reason) OVERRIDE;
+ virtual void SetConnectionListener(ConnectionListener* listener) OVERRIDE;
+
+ // Whether a connection reset has been triggered and is yet to run.
+ bool reconnect_pending() const { return reconnect_pending_; }
+
+ // Whether connection resets should be handled immediately or delayed until
+ // release.
+ void set_delay_reconnect(bool should_delay) {
+ delay_reconnect_ = should_delay;
+ }
private:
scoped_ptr<FakeConnectionHandler> connection_handler_;
BuildLoginRequestCallback request_builder_;
+ // Logic for handling connection resets.
+ bool reconnect_pending_;
+ bool delay_reconnect_;
+
DISALLOW_COPY_AND_ASSIGN(FakeConnectionFactory);
};
diff --git a/chromium/google_apis/gcm/engine/fake_connection_handler.cc b/chromium/google_apis/gcm/engine/fake_connection_handler.cc
index 06639331ebe..3e5d5ac2770 100644
--- a/chromium/google_apis/gcm/engine/fake_connection_handler.cc
+++ b/chromium/google_apis/gcm/engine/fake_connection_handler.cc
@@ -39,7 +39,8 @@ FakeConnectionHandler::~FakeConnectionHandler() {
}
void FakeConnectionHandler::Init(const mcs_proto::LoginRequest& login_request,
- scoped_ptr<net::StreamSocket> socket) {
+ net::StreamSocket* socket) {
+ ASSERT_GE(expected_outgoing_messages_.size(), 1U);
EXPECT_EQ(expected_outgoing_messages_.front().SerializeAsString(),
login_request.SerializeAsString());
expected_outgoing_messages_.pop_front();
@@ -48,6 +49,10 @@ void FakeConnectionHandler::Init(const mcs_proto::LoginRequest& login_request,
initialized_ = !fail_login_;
}
+void FakeConnectionHandler::Reset() {
+ initialized_ = false;
+}
+
bool FakeConnectionHandler::CanSendMessage() const {
return initialized_;
}
diff --git a/chromium/google_apis/gcm/engine/fake_connection_handler.h b/chromium/google_apis/gcm/engine/fake_connection_handler.h
index 5356b771086..5229e3fb49d 100644
--- a/chromium/google_apis/gcm/engine/fake_connection_handler.h
+++ b/chromium/google_apis/gcm/engine/fake_connection_handler.h
@@ -23,7 +23,8 @@ class FakeConnectionHandler : public ConnectionHandler {
// ConnectionHandler implementation.
virtual void Init(const mcs_proto::LoginRequest& login_request,
- scoped_ptr<net::StreamSocket> socket) OVERRIDE;
+ net::StreamSocket* socket) OVERRIDE;
+ virtual void Reset() OVERRIDE;
virtual bool CanSendMessage() const OVERRIDE;
virtual void SendMessage(const google::protobuf::MessageLite& message)
OVERRIDE;
diff --git a/chromium/google_apis/gcm/engine/gcm_store.cc b/chromium/google_apis/gcm/engine/gcm_store.cc
new file mode 100644
index 00000000000..91ad60fe852
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gcm_store.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 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 "google_apis/gcm/engine/gcm_store.h"
+
+namespace gcm {
+
+GCMStore::LoadResult::LoadResult()
+ : success(false),
+ device_android_id(0),
+ device_security_token(0) {
+}
+
+GCMStore::LoadResult::~LoadResult() {}
+
+GCMStore::GCMStore() {}
+
+GCMStore::~GCMStore() {}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/gcm_store.h b/chromium/google_apis/gcm/engine/gcm_store.h
new file mode 100644
index 00000000000..8b9891d0ecc
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gcm_store.h
@@ -0,0 +1,121 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_GCM_STORE_H_
+#define GOOGLE_APIS_GCM_ENGINE_GCM_STORE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <google/protobuf/message_lite.h>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "google_apis/gcm/engine/registration_info.h"
+
+namespace gcm {
+
+class MCSMessage;
+
+// A GCM data store interface. GCM Store will handle persistence portion of RMQ,
+// as well as store device and user checkin information.
+class GCM_EXPORT GCMStore {
+ public:
+ // Map of message id to message data for outgoing messages.
+ typedef std::map<std::string, linked_ptr<google::protobuf::MessageLite> >
+ OutgoingMessageMap;
+
+ // Container for Load(..) results.
+ struct GCM_EXPORT LoadResult {
+ LoadResult();
+ ~LoadResult();
+
+ bool success;
+ uint64 device_android_id;
+ uint64 device_security_token;
+ RegistrationInfoMap registrations;
+ std::vector<std::string> incoming_messages;
+ OutgoingMessageMap outgoing_messages;
+ std::map<std::string, std::string> gservices_settings;
+ std::string gservices_digest;
+ base::Time last_checkin_time;
+ };
+
+ typedef std::vector<std::string> PersistentIdList;
+ typedef base::Callback<void(scoped_ptr<LoadResult> result)> LoadCallback;
+ typedef base::Callback<void(bool success)> UpdateCallback;
+
+ GCMStore();
+ virtual ~GCMStore();
+
+ // Load the data from persistent store and pass the initial state back to
+ // caller.
+ virtual void Load(const LoadCallback& callback) = 0;
+
+ // Close the persistent store.
+ virtual void Close() = 0;
+
+ // Clears the GCM store of all data.
+ virtual void Destroy(const UpdateCallback& callback) = 0;
+
+ // Sets this device's messaging credentials.
+ virtual void SetDeviceCredentials(uint64 device_android_id,
+ uint64 device_security_token,
+ const UpdateCallback& callback) = 0;
+
+ // Registration info.
+ virtual void AddRegistration(const std::string& app_id,
+ const linked_ptr<RegistrationInfo>& registration,
+ const UpdateCallback& callback) = 0;
+ virtual void RemoveRegistration(const std::string& app_id,
+ const UpdateCallback& callback) = 0;
+
+ // Unacknowledged incoming message handling.
+ virtual void AddIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) = 0;
+ virtual void RemoveIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) = 0;
+ virtual void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) = 0;
+
+ // Unacknowledged outgoing messages handling.
+ // Returns false if app has surpassed message limits, else returns true. Note
+ // that the message isn't persisted until |callback| is invoked with
+ // |success| == true.
+ virtual bool AddOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) = 0;
+ virtual void OverwriteOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) = 0;
+ virtual void RemoveOutgoingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) = 0;
+ virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) = 0;
+
+ // Sets last device's checkin time.
+ virtual void SetLastCheckinTime(const base::Time& last_checkin_time,
+ const UpdateCallback& callback) = 0;
+
+ // G-service settings handling.
+ // Persists |settings| and |settings_digest|. It completely replaces the
+ // existing data.
+ virtual void SetGServicesSettings(
+ const std::map<std::string, std::string>& settings,
+ const std::string& settings_digest,
+ const UpdateCallback& callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GCMStore);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_GCM_STORE_H_
diff --git a/chromium/google_apis/gcm/engine/gcm_store_impl.cc b/chromium/google_apis/gcm/engine/gcm_store_impl.cc
new file mode 100644
index 00000000000..e27e82e6ce2
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gcm_store_impl.cc
@@ -0,0 +1,956 @@
+// Copyright 2014 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 "google_apis/gcm/engine/gcm_store_impl.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "google_apis/gcm/base/encryptor.h"
+#include "google_apis/gcm/base/mcs_message.h"
+#include "google_apis/gcm/base/mcs_util.h"
+#include "google_apis/gcm/protocol/mcs.pb.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace gcm {
+
+namespace {
+
+// Limit to the number of outstanding messages per app.
+const int kMessagesPerAppLimit = 20;
+
+// ---- LevelDB keys. ----
+// Key for this device's android id.
+const char kDeviceAIDKey[] = "device_aid_key";
+// Key for this device's android security token.
+const char kDeviceTokenKey[] = "device_token_key";
+// Lowest lexicographically ordered app ids.
+// Used for prefixing app id.
+const char kRegistrationKeyStart[] = "reg1-";
+// Key guaranteed to be higher than all app ids.
+// Used for limiting iteration.
+const char kRegistrationKeyEnd[] = "reg2-";
+// Lowest lexicographically ordered incoming message key.
+// Used for prefixing messages.
+const char kIncomingMsgKeyStart[] = "incoming1-";
+// Key guaranteed to be higher than all incoming message keys.
+// Used for limiting iteration.
+const char kIncomingMsgKeyEnd[] = "incoming2-";
+// Lowest lexicographically ordered outgoing message key.
+// Used for prefixing outgoing messages.
+const char kOutgoingMsgKeyStart[] = "outgoing1-";
+// Key guaranteed to be higher than all outgoing message keys.
+// Used for limiting iteration.
+const char kOutgoingMsgKeyEnd[] = "outgoing2-";
+// Lowest lexicographically ordered G-service settings key.
+// Used for prefixing G-services settings.
+const char kGServiceSettingKeyStart[] = "gservice1-";
+// Key guaranteed to be higher than all G-services settings keys.
+// Used for limiting iteration.
+const char kGServiceSettingKeyEnd[] = "gservice2-";
+// Key for digest of the last G-services settings update.
+const char kGServiceSettingsDigestKey[] = "gservices_digest";
+// Key used to timestamp last checkin (marked with G services settings update).
+const char kLastCheckinTimeKey[] = "last_checkin_time";
+
+std::string MakeRegistrationKey(const std::string& app_id) {
+ return kRegistrationKeyStart + app_id;
+}
+
+std::string ParseRegistrationKey(const std::string& key) {
+ return key.substr(arraysize(kRegistrationKeyStart) - 1);
+}
+
+std::string MakeIncomingKey(const std::string& persistent_id) {
+ return kIncomingMsgKeyStart + persistent_id;
+}
+
+std::string MakeOutgoingKey(const std::string& persistent_id) {
+ return kOutgoingMsgKeyStart + persistent_id;
+}
+
+std::string ParseOutgoingKey(const std::string& key) {
+ return key.substr(arraysize(kOutgoingMsgKeyStart) - 1);
+}
+
+std::string MakeGServiceSettingKey(const std::string& setting_name) {
+ return kGServiceSettingKeyStart + setting_name;
+}
+
+std::string ParseGServiceSettingKey(const std::string& key) {
+ return key.substr(arraysize(kGServiceSettingKeyStart) - 1);
+}
+
+// Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore
+// outlive the slice.
+// For example: MakeSlice(MakeOutgoingKey(x)) is invalid.
+leveldb::Slice MakeSlice(const base::StringPiece& s) {
+ return leveldb::Slice(s.begin(), s.size());
+}
+
+} // namespace
+
+class GCMStoreImpl::Backend
+ : public base::RefCountedThreadSafe<GCMStoreImpl::Backend> {
+ public:
+ Backend(const base::FilePath& path,
+ scoped_refptr<base::SequencedTaskRunner> foreground_runner,
+ scoped_ptr<Encryptor> encryptor);
+
+ // Blocking implementations of GCMStoreImpl methods.
+ void Load(const LoadCallback& callback);
+ void Close();
+ void Destroy(const UpdateCallback& callback);
+ void SetDeviceCredentials(uint64 device_android_id,
+ uint64 device_security_token,
+ const UpdateCallback& callback);
+ void AddRegistration(const std::string& app_id,
+ const linked_ptr<RegistrationInfo>& registration,
+ const UpdateCallback& callback);
+ void RemoveRegistration(const std::string& app_id,
+ const UpdateCallback& callback);
+ void AddIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback);
+ void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback);
+ void AddOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback);
+ void RemoveOutgoingMessages(
+ const PersistentIdList& persistent_ids,
+ const base::Callback<void(bool, const AppIdToMessageCountMap&)>
+ callback);
+ void AddUserSerialNumber(const std::string& username,
+ int64 serial_number,
+ const UpdateCallback& callback);
+ void RemoveUserSerialNumber(const std::string& username,
+ const UpdateCallback& callback);
+ void SetLastCheckinTime(const base::Time& last_checkin_time,
+ const UpdateCallback& callback);
+ void SetGServicesSettings(
+ const std::map<std::string, std::string>& settings,
+ const std::string& digest,
+ const UpdateCallback& callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<Backend>;
+ ~Backend();
+
+ bool LoadDeviceCredentials(uint64* android_id, uint64* security_token);
+ bool LoadRegistrations(RegistrationInfoMap* registrations);
+ bool LoadIncomingMessages(std::vector<std::string>* incoming_messages);
+ bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages);
+ bool LoadLastCheckinTime(base::Time* last_checkin_time);
+ bool LoadGServicesSettings(std::map<std::string, std::string>* settings,
+ std::string* digest);
+
+ const base::FilePath path_;
+ scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_;
+ scoped_ptr<Encryptor> encryptor_;
+
+ scoped_ptr<leveldb::DB> db_;
+};
+
+GCMStoreImpl::Backend::Backend(
+ const base::FilePath& path,
+ scoped_refptr<base::SequencedTaskRunner> foreground_task_runner,
+ scoped_ptr<Encryptor> encryptor)
+ : path_(path),
+ foreground_task_runner_(foreground_task_runner),
+ encryptor_(encryptor.Pass()) {
+}
+
+GCMStoreImpl::Backend::~Backend() {}
+
+void GCMStoreImpl::Backend::Load(const LoadCallback& callback) {
+ scoped_ptr<LoadResult> result(new LoadResult());
+ if (db_.get()) {
+ LOG(ERROR) << "Attempting to reload open database.";
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ base::Passed(&result)));
+ return;
+ }
+
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status =
+ leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db);
+ UMA_HISTOGRAM_BOOLEAN("GCM.LoadSucceeded", status.ok());
+ if (!status.ok()) {
+ LOG(ERROR) << "Failed to open database " << path_.value() << ": "
+ << status.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ base::Passed(&result)));
+ return;
+ }
+ db_.reset(db);
+
+ if (!LoadDeviceCredentials(&result->device_android_id,
+ &result->device_security_token) ||
+ !LoadRegistrations(&result->registrations) ||
+ !LoadIncomingMessages(&result->incoming_messages) ||
+ !LoadOutgoingMessages(&result->outgoing_messages) ||
+ !LoadLastCheckinTime(&result->last_checkin_time) ||
+ !LoadGServicesSettings(&result->gservices_settings,
+ &result->gservices_digest)) {
+ result->device_android_id = 0;
+ result->device_security_token = 0;
+ result->registrations.clear();
+ result->incoming_messages.clear();
+ result->outgoing_messages.clear();
+ result->gservices_settings.clear();
+ result->gservices_digest.clear();
+ result->last_checkin_time = base::Time::FromInternalValue(0LL);
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ base::Passed(&result)));
+ return;
+ }
+
+ // Only record histograms if GCM had already been set up for this device.
+ if (result->device_android_id != 0 && result->device_security_token != 0) {
+ int64 file_size = 0;
+ if (base::GetFileSize(path_, &file_size)) {
+ UMA_HISTOGRAM_COUNTS("GCM.StoreSizeKB",
+ static_cast<int>(file_size / 1024));
+ }
+ UMA_HISTOGRAM_COUNTS("GCM.RestoredRegistrations",
+ result->registrations.size());
+ UMA_HISTOGRAM_COUNTS("GCM.RestoredOutgoingMessages",
+ result->outgoing_messages.size());
+ UMA_HISTOGRAM_COUNTS("GCM.RestoredIncomingMessages",
+ result->incoming_messages.size());
+ }
+
+ DVLOG(1) << "Succeeded in loading " << result->registrations.size()
+ << " registrations, "
+ << result->incoming_messages.size()
+ << " unacknowledged incoming messages and "
+ << result->outgoing_messages.size()
+ << " unacknowledged outgoing messages.";
+ result->success = true;
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ base::Passed(&result)));
+ return;
+}
+
+void GCMStoreImpl::Backend::Close() {
+ DVLOG(1) << "Closing GCM store.";
+ db_.reset();
+}
+
+void GCMStoreImpl::Backend::Destroy(const UpdateCallback& callback) {
+ DVLOG(1) << "Destroying GCM store.";
+ db_.reset();
+ const leveldb::Status s =
+ leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options());
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "Destroy failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::SetDeviceCredentials(
+ uint64 device_android_id,
+ uint64 device_security_token,
+ const UpdateCallback& callback) {
+ DVLOG(1) << "Saving device credentials with AID " << device_android_id;
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ std::string encrypted_token;
+ encryptor_->EncryptString(base::Uint64ToString(device_security_token),
+ &encrypted_token);
+ std::string android_id_str = base::Uint64ToString(device_android_id);
+ leveldb::Status s =
+ db_->Put(write_options,
+ MakeSlice(kDeviceAIDKey),
+ MakeSlice(android_id_str));
+ if (s.ok()) {
+ s = db_->Put(
+ write_options, MakeSlice(kDeviceTokenKey), MakeSlice(encrypted_token));
+ }
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB put failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::AddRegistration(
+ const std::string& app_id,
+ const linked_ptr<RegistrationInfo>& registration,
+ const UpdateCallback& callback) {
+ DVLOG(1) << "Saving registration info for app: " << app_id;
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ std::string key = MakeRegistrationKey(app_id);
+ std::string value = registration->SerializeAsString();
+ const leveldb::Status status = db_->Put(write_options,
+ MakeSlice(key),
+ MakeSlice(value));
+ if (status.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB put failed: " << status.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::RemoveRegistration(const std::string& app_id,
+ const UpdateCallback& callback) {
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ leveldb::Status status = db_->Delete(write_options, MakeSlice(app_id));
+ if (status.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB remove failed: " << status.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::AddIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) {
+ DVLOG(1) << "Saving incoming message with id " << persistent_id;
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ std::string key = MakeIncomingKey(persistent_id);
+ const leveldb::Status s = db_->Put(write_options,
+ MakeSlice(key),
+ MakeSlice(persistent_id));
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB put failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::RemoveIncomingMessages(
+ const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) {
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ leveldb::Status s;
+ for (PersistentIdList::const_iterator iter = persistent_ids.begin();
+ iter != persistent_ids.end();
+ ++iter) {
+ DVLOG(1) << "Removing incoming message with id " << *iter;
+ std::string key = MakeIncomingKey(*iter);
+ s = db_->Delete(write_options, MakeSlice(key));
+ if (!s.ok())
+ break;
+ }
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::AddOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) {
+ DVLOG(1) << "Saving outgoing message with id " << persistent_id;
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ std::string data =
+ static_cast<char>(message.tag()) + message.SerializeAsString();
+ std::string key = MakeOutgoingKey(persistent_id);
+ const leveldb::Status s = db_->Put(write_options,
+ MakeSlice(key),
+ MakeSlice(data));
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
+ return;
+ }
+ LOG(ERROR) << "LevelDB put failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
+}
+
+void GCMStoreImpl::Backend::RemoveOutgoingMessages(
+ const PersistentIdList& persistent_ids,
+ const base::Callback<void(bool, const AppIdToMessageCountMap&)>
+ callback) {
+ if (!db_.get()) {
+ LOG(ERROR) << "GCMStore db doesn't exist.";
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ false,
+ AppIdToMessageCountMap()));
+ return;
+ }
+ leveldb::ReadOptions read_options;
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ AppIdToMessageCountMap removed_message_counts;
+
+ leveldb::Status s;
+ for (PersistentIdList::const_iterator iter = persistent_ids.begin();
+ iter != persistent_ids.end();
+ ++iter) {
+ DVLOG(1) << "Removing outgoing message with id " << *iter;
+ std::string outgoing_message;
+ std::string key = MakeOutgoingKey(*iter);
+ s = db_->Get(read_options,
+ MakeSlice(key),
+ &outgoing_message);
+ if (!s.ok())
+ break;
+ mcs_proto::DataMessageStanza data_message;
+ // Skip the initial tag byte and parse the rest to extract the message.
+ if (data_message.ParseFromString(outgoing_message.substr(1))) {
+ DCHECK(!data_message.category().empty());
+ if (removed_message_counts.count(data_message.category()) != 0)
+ removed_message_counts[data_message.category()]++;
+ else
+ removed_message_counts[data_message.category()] = 1;
+ }
+ DVLOG(1) << "Removing outgoing message with id " << *iter;
+ s = db_->Delete(write_options, MakeSlice(key));
+ if (!s.ok())
+ break;
+ }
+ if (s.ok()) {
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ true,
+ removed_message_counts));
+ return;
+ }
+ LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ false,
+ AppIdToMessageCountMap()));
+}
+
+void GCMStoreImpl::Backend::SetLastCheckinTime(
+ const base::Time& last_checkin_time,
+ const UpdateCallback& callback) {
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ int64 last_checkin_time_internal = last_checkin_time.ToInternalValue();
+ const leveldb::Status s =
+ db_->Put(write_options,
+ MakeSlice(kLastCheckinTimeKey),
+ MakeSlice(base::Int64ToString(last_checkin_time_internal)));
+
+ if (!s.ok())
+ LOG(ERROR) << "LevelDB set last checkin time failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
+}
+
+void GCMStoreImpl::Backend::SetGServicesSettings(
+ const std::map<std::string, std::string>& settings,
+ const std::string& settings_digest,
+ const UpdateCallback& callback) {
+ leveldb::WriteBatch write_batch;
+
+ // Remove all existing settings.
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
+ for (iter->Seek(MakeSlice(kGServiceSettingKeyStart));
+ iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd;
+ iter->Next()) {
+ write_batch.Delete(iter->key());
+ }
+
+ // Add the new settings.
+ for (std::map<std::string, std::string>::const_iterator iter =
+ settings.begin();
+ iter != settings.end(); ++iter) {
+ write_batch.Put(MakeSlice(MakeGServiceSettingKey(iter->first)),
+ MakeSlice(iter->second));
+ }
+
+ // Update the settings digest.
+ write_batch.Put(MakeSlice(kGServiceSettingsDigestKey),
+ MakeSlice(settings_digest));
+
+ // Write it all in a batch.
+ leveldb::WriteOptions write_options;
+ write_options.sync = true;
+
+ leveldb::Status s = db_->Write(write_options, &write_batch);
+ if (!s.ok())
+ LOG(ERROR) << "LevelDB GService Settings update failed: " << s.ToString();
+ foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
+}
+
+bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id,
+ uint64* security_token) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ std::string result;
+ leveldb::Status s = db_->Get(read_options, MakeSlice(kDeviceAIDKey), &result);
+ if (s.ok()) {
+ if (!base::StringToUint64(result, android_id)) {
+ LOG(ERROR) << "Failed to restore device id.";
+ return false;
+ }
+ result.clear();
+ s = db_->Get(read_options, MakeSlice(kDeviceTokenKey), &result);
+ }
+ if (s.ok()) {
+ std::string decrypted_token;
+ encryptor_->DecryptString(result, &decrypted_token);
+ if (!base::StringToUint64(decrypted_token, security_token)) {
+ LOG(ERROR) << "Failed to restore security token.";
+ return false;
+ }
+ return true;
+ }
+
+ if (s.IsNotFound()) {
+ DVLOG(1) << "No credentials found.";
+ return true;
+ }
+
+ LOG(ERROR) << "Error reading credentials from store.";
+ return false;
+}
+
+bool GCMStoreImpl::Backend::LoadRegistrations(
+ RegistrationInfoMap* registrations) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
+ for (iter->Seek(MakeSlice(kRegistrationKeyStart));
+ iter->Valid() && iter->key().ToString() < kRegistrationKeyEnd;
+ iter->Next()) {
+ leveldb::Slice s = iter->value();
+ if (s.size() <= 1) {
+ LOG(ERROR) << "Error reading registration with key " << s.ToString();
+ return false;
+ }
+ std::string app_id = ParseRegistrationKey(iter->key().ToString());
+ linked_ptr<RegistrationInfo> registration(new RegistrationInfo);
+ if (!registration->ParseFromString(iter->value().ToString())) {
+ LOG(ERROR) << "Failed to parse registration with app id " << app_id;
+ return false;
+ }
+ DVLOG(1) << "Found registration with app id " << app_id;
+ (*registrations)[app_id] = registration;
+ }
+
+ return true;
+}
+
+bool GCMStoreImpl::Backend::LoadIncomingMessages(
+ std::vector<std::string>* incoming_messages) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
+ for (iter->Seek(MakeSlice(kIncomingMsgKeyStart));
+ iter->Valid() && iter->key().ToString() < kIncomingMsgKeyEnd;
+ iter->Next()) {
+ leveldb::Slice s = iter->value();
+ if (s.empty()) {
+ LOG(ERROR) << "Error reading incoming message with key "
+ << iter->key().ToString();
+ return false;
+ }
+ DVLOG(1) << "Found incoming message with id " << s.ToString();
+ incoming_messages->push_back(s.ToString());
+ }
+
+ return true;
+}
+
+bool GCMStoreImpl::Backend::LoadOutgoingMessages(
+ OutgoingMessageMap* outgoing_messages) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
+ for (iter->Seek(MakeSlice(kOutgoingMsgKeyStart));
+ iter->Valid() && iter->key().ToString() < kOutgoingMsgKeyEnd;
+ iter->Next()) {
+ leveldb::Slice s = iter->value();
+ if (s.size() <= 1) {
+ LOG(ERROR) << "Error reading incoming message with key " << s.ToString();
+ return false;
+ }
+ uint8 tag = iter->value().data()[0];
+ std::string id = ParseOutgoingKey(iter->key().ToString());
+ scoped_ptr<google::protobuf::MessageLite> message(
+ BuildProtobufFromTag(tag));
+ if (!message.get() ||
+ !message->ParseFromString(iter->value().ToString().substr(1))) {
+ LOG(ERROR) << "Failed to parse outgoing message with id " << id
+ << " and tag " << tag;
+ return false;
+ }
+ DVLOG(1) << "Found outgoing message with id " << id << " of type "
+ << base::IntToString(tag);
+ (*outgoing_messages)[id] = make_linked_ptr(message.release());
+ }
+
+ return true;
+}
+
+bool GCMStoreImpl::Backend::LoadLastCheckinTime(
+ base::Time* last_checkin_time) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ std::string result;
+ leveldb::Status s = db_->Get(read_options,
+ MakeSlice(kLastCheckinTimeKey),
+ &result);
+ int64 time_internal = 0LL;
+ if (s.ok() && !base::StringToInt64(result, &time_internal))
+ LOG(ERROR) << "Failed to restore last checkin time. Using default = 0.";
+
+ // In case we cannot read last checkin time, we default it to 0, as we don't
+ // want that situation to cause the whole load to fail.
+ *last_checkin_time = base::Time::FromInternalValue(time_internal);
+
+ return true;
+}
+
+bool GCMStoreImpl::Backend::LoadGServicesSettings(
+ std::map<std::string, std::string>* settings,
+ std::string* digest) {
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+
+ // Load all of the GServices settings.
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
+ for (iter->Seek(MakeSlice(kGServiceSettingKeyStart));
+ iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd;
+ iter->Next()) {
+ std::string value = iter->value().ToString();
+ if (value.empty()) {
+ LOG(ERROR) << "Error reading GService Settings " << value;
+ return false;
+ }
+ std::string id = ParseGServiceSettingKey(iter->key().ToString());
+ (*settings)[id] = value;
+ DVLOG(1) << "Found G Service setting with key: " << id
+ << ", and value: " << value;
+ }
+
+ // Load the settings digest. It's ok if it is empty.
+ db_->Get(read_options, MakeSlice(kGServiceSettingsDigestKey), digest);
+
+ return true;
+}
+
+GCMStoreImpl::GCMStoreImpl(
+ const base::FilePath& path,
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+ scoped_ptr<Encryptor> encryptor)
+ : backend_(new Backend(path,
+ base::MessageLoopProxy::current(),
+ encryptor.Pass())),
+ blocking_task_runner_(blocking_task_runner),
+ weak_ptr_factory_(this) {
+}
+
+GCMStoreImpl::~GCMStoreImpl() {}
+
+void GCMStoreImpl::Load(const LoadCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::Load,
+ backend_,
+ base::Bind(&GCMStoreImpl::LoadContinuation,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback)));
+}
+
+void GCMStoreImpl::Close() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ app_message_counts_.clear();
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::Close, backend_));
+}
+
+void GCMStoreImpl::Destroy(const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::Destroy, backend_, callback));
+}
+
+void GCMStoreImpl::SetDeviceCredentials(uint64 device_android_id,
+ uint64 device_security_token,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::SetDeviceCredentials,
+ backend_,
+ device_android_id,
+ device_security_token,
+ callback));
+}
+
+void GCMStoreImpl::AddRegistration(
+ const std::string& app_id,
+ const linked_ptr<RegistrationInfo>& registration,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::AddRegistration,
+ backend_,
+ app_id,
+ registration,
+ callback));
+}
+
+void GCMStoreImpl::RemoveRegistration(const std::string& app_id,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::RemoveRegistration,
+ backend_,
+ app_id,
+ callback));
+}
+
+void GCMStoreImpl::AddIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::AddIncomingMessage,
+ backend_,
+ persistent_id,
+ callback));
+}
+
+void GCMStoreImpl::RemoveIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::RemoveIncomingMessages,
+ backend_,
+ PersistentIdList(1, persistent_id),
+ callback));
+}
+
+void GCMStoreImpl::RemoveIncomingMessages(
+ const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::RemoveIncomingMessages,
+ backend_,
+ persistent_ids,
+ callback));
+}
+
+bool GCMStoreImpl::AddOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) {
+ DCHECK_EQ(message.tag(), kDataMessageStanzaTag);
+ std::string app_id = reinterpret_cast<const mcs_proto::DataMessageStanza*>(
+ &message.GetProtobuf())->category();
+ DCHECK(!app_id.empty());
+ if (app_message_counts_.count(app_id) == 0)
+ app_message_counts_[app_id] = 0;
+ if (app_message_counts_[app_id] < kMessagesPerAppLimit) {
+ app_message_counts_[app_id]++;
+
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::AddOutgoingMessage,
+ backend_,
+ persistent_id,
+ message,
+ base::Bind(&GCMStoreImpl::AddOutgoingMessageContinuation,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ app_id)));
+ return true;
+ }
+ return false;
+}
+
+void GCMStoreImpl::OverwriteOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) {
+ DCHECK_EQ(message.tag(), kDataMessageStanzaTag);
+ std::string app_id = reinterpret_cast<const mcs_proto::DataMessageStanza*>(
+ &message.GetProtobuf())->category();
+ DCHECK(!app_id.empty());
+ // There should already be pending messages for this app.
+ DCHECK(app_message_counts_.count(app_id));
+ // TODO(zea): consider verifying the specific message already exists.
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::AddOutgoingMessage,
+ backend_,
+ persistent_id,
+ message,
+ callback));
+}
+
+void GCMStoreImpl::RemoveOutgoingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::RemoveOutgoingMessages,
+ backend_,
+ PersistentIdList(1, persistent_id),
+ base::Bind(&GCMStoreImpl::RemoveOutgoingMessagesContinuation,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback)));
+}
+
+void GCMStoreImpl::RemoveOutgoingMessages(
+ const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::RemoveOutgoingMessages,
+ backend_,
+ persistent_ids,
+ base::Bind(&GCMStoreImpl::RemoveOutgoingMessagesContinuation,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback)));
+}
+
+void GCMStoreImpl::SetLastCheckinTime(const base::Time& last_checkin_time,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::SetLastCheckinTime,
+ backend_,
+ last_checkin_time,
+ callback));
+}
+
+void GCMStoreImpl::SetGServicesSettings(
+ const std::map<std::string, std::string>& settings,
+ const std::string& digest,
+ const UpdateCallback& callback) {
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&GCMStoreImpl::Backend::SetGServicesSettings,
+ backend_,
+ settings,
+ digest,
+ callback));
+}
+
+void GCMStoreImpl::LoadContinuation(const LoadCallback& callback,
+ scoped_ptr<LoadResult> result) {
+ if (!result->success) {
+ callback.Run(result.Pass());
+ return;
+ }
+ int num_throttled_apps = 0;
+ for (OutgoingMessageMap::const_iterator
+ iter = result->outgoing_messages.begin();
+ iter != result->outgoing_messages.end(); ++iter) {
+ const mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(iter->second.get());
+ DCHECK(!data_message->category().empty());
+ if (app_message_counts_.count(data_message->category()) == 0)
+ app_message_counts_[data_message->category()] = 1;
+ else
+ app_message_counts_[data_message->category()]++;
+ if (app_message_counts_[data_message->category()] == kMessagesPerAppLimit)
+ num_throttled_apps++;
+ }
+ UMA_HISTOGRAM_COUNTS("GCM.NumThrottledApps", num_throttled_apps);
+ callback.Run(result.Pass());
+}
+
+void GCMStoreImpl::AddOutgoingMessageContinuation(
+ const UpdateCallback& callback,
+ const std::string& app_id,
+ bool success) {
+ if (!success) {
+ DCHECK(app_message_counts_[app_id] > 0);
+ app_message_counts_[app_id]--;
+ }
+ callback.Run(success);
+}
+
+void GCMStoreImpl::RemoveOutgoingMessagesContinuation(
+ const UpdateCallback& callback,
+ bool success,
+ const AppIdToMessageCountMap& removed_message_counts) {
+ if (!success) {
+ callback.Run(false);
+ return;
+ }
+ for (AppIdToMessageCountMap::const_iterator iter =
+ removed_message_counts.begin();
+ iter != removed_message_counts.end(); ++iter) {
+ DCHECK_NE(app_message_counts_.count(iter->first), 0U);
+ app_message_counts_[iter->first] -= iter->second;
+ DCHECK_GE(app_message_counts_[iter->first], 0);
+ }
+ callback.Run(true);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/gcm_store_impl.h b/chromium/google_apis/gcm/engine/gcm_store_impl.h
new file mode 100644
index 00000000000..f49509a3e17
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gcm_store_impl.h
@@ -0,0 +1,126 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_GCM_STORE_IMPL_H_
+#define GOOGLE_APIS_GCM_ENGINE_GCM_STORE_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "google_apis/gcm/engine/gcm_store.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace gcm {
+
+class Encryptor;
+
+// An implementation of GCM Store that uses LevelDB for persistence.
+// It performs all blocking operations on the blocking task runner, and posts
+// all callbacks to the thread on which the GCMStoreImpl is created.
+class GCM_EXPORT GCMStoreImpl : public GCMStore {
+ public:
+ GCMStoreImpl(const base::FilePath& path,
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+ scoped_ptr<Encryptor> encryptor);
+ virtual ~GCMStoreImpl();
+
+ // Load the directory and pass the initial state back to caller.
+ virtual void Load(const LoadCallback& callback) OVERRIDE;
+
+ // Closes the GCM store.
+ virtual void Close() OVERRIDE;
+
+ // Clears the GCM store of all data and destroys any LevelDB files associated
+ // with this store.
+ // WARNING: this will permanently destroy any pending outgoing messages
+ // and require the device to re-create credentials and serial number mapping
+ // tables.
+ virtual void Destroy(const UpdateCallback& callback) OVERRIDE;
+
+ // Sets this device's messaging credentials.
+ virtual void SetDeviceCredentials(uint64 device_android_id,
+ uint64 device_security_token,
+ const UpdateCallback& callback) OVERRIDE;
+
+ // Registration info.
+ virtual void AddRegistration(const std::string& app_id,
+ const linked_ptr<RegistrationInfo>& registration,
+ const UpdateCallback& callback) OVERRIDE;
+ virtual void RemoveRegistration(const std::string& app_id,
+ const UpdateCallback& callback) OVERRIDE;
+
+ // Unacknowledged incoming message handling.
+ virtual void AddIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) OVERRIDE;
+ virtual void RemoveIncomingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) OVERRIDE;
+ virtual void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) OVERRIDE;
+
+ // Unacknowledged outgoing messages handling.
+ virtual bool AddOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback) OVERRIDE;
+ virtual void OverwriteOutgoingMessage(const std::string& persistent_id,
+ const MCSMessage& message,
+ const UpdateCallback& callback)
+ OVERRIDE;
+ virtual void RemoveOutgoingMessage(const std::string& persistent_id,
+ const UpdateCallback& callback) OVERRIDE;
+ virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
+ const UpdateCallback& callback) OVERRIDE;
+
+ // Sets last device's checkin time.
+ virtual void SetLastCheckinTime(const base::Time& last_checkin_time,
+ const UpdateCallback& callback) OVERRIDE;
+
+ // G-service settings handling.
+ virtual void SetGServicesSettings(
+ const std::map<std::string, std::string>& settings,
+ const std::string& settings_digest,
+ const UpdateCallback& callback) OVERRIDE;
+
+ private:
+ typedef std::map<std::string, int> AppIdToMessageCountMap;
+
+ // Continuation to update the per-app message counts after a load.
+ void LoadContinuation(const LoadCallback& callback,
+ scoped_ptr<LoadResult> result);
+
+ // Continuation to update the per-app message counts when adding messages.
+ // In particular, if a message fails to add, the message count is decremented.
+ void AddOutgoingMessageContinuation(const UpdateCallback& callback,
+ const std::string& app_id,
+ bool success);
+
+ // Continuation to update the per-app message counts when removing messages.
+ // Note: if doing a read-then-write when removing messages proves expensive,
+ // an in-memory mapping of persisted message id to app could be maintained
+ // instead.
+ void RemoveOutgoingMessagesContinuation(
+ const UpdateCallback& callback,
+ bool success,
+ const std::map<std::string, int>& removed_message_counts);
+
+ class Backend;
+
+ // Map of App ids to their message counts.
+ AppIdToMessageCountMap app_message_counts_;
+
+ scoped_refptr<Backend> backend_;
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+
+ base::WeakPtrFactory<GCMStoreImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCMStoreImpl);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_GCM_STORE_IMPL_H_
diff --git a/chromium/google_apis/gcm/engine/gcm_store_impl_unittest.cc b/chromium/google_apis/gcm/engine/gcm_store_impl_unittest.cc
new file mode 100644
index 00000000000..7b9c8936529
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gcm_store_impl_unittest.cc
@@ -0,0 +1,552 @@
+// Copyright 2014 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 "google_apis/gcm/engine/gcm_store_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "google_apis/gcm/base/fake_encryptor.h"
+#include "google_apis/gcm/base/mcs_message.h"
+#include "google_apis/gcm/base/mcs_util.h"
+#include "google_apis/gcm/protocol/mcs.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+// Number of persistent ids to use in tests.
+const int kNumPersistentIds = 10;
+
+// Number of per-app messages in tests.
+const int kNumMessagesPerApp = 20;
+
+// App name for testing.
+const char kAppName[] = "my_app";
+
+// Category name for testing.
+const char kCategoryName[] = "my_category";
+
+const uint64 kDeviceId = 22;
+const uint64 kDeviceToken = 55;
+
+class GCMStoreImplTest : public testing::Test {
+ public:
+ GCMStoreImplTest();
+ virtual ~GCMStoreImplTest();
+
+ virtual void SetUp() OVERRIDE;
+
+ scoped_ptr<GCMStore> BuildGCMStore();
+
+ std::string GetNextPersistentId();
+
+ void PumpLoop();
+
+ void LoadCallback(scoped_ptr<GCMStore::LoadResult>* result_dst,
+ scoped_ptr<GCMStore::LoadResult> result);
+ void UpdateCallback(bool success);
+
+ protected:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_directory_;
+ bool expected_success_;
+ uint64 next_persistent_id_;
+ scoped_ptr<base::RunLoop> run_loop_;
+};
+
+GCMStoreImplTest::GCMStoreImplTest()
+ : expected_success_(true),
+ next_persistent_id_(base::Time::Now().ToInternalValue()) {
+ EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
+ run_loop_.reset(new base::RunLoop());
+}
+
+GCMStoreImplTest::~GCMStoreImplTest() {}
+
+void GCMStoreImplTest::SetUp() {
+ testing::Test::SetUp();
+}
+
+scoped_ptr<GCMStore> GCMStoreImplTest::BuildGCMStore() {
+ return scoped_ptr<GCMStore>(new GCMStoreImpl(
+ temp_directory_.path(),
+ message_loop_.message_loop_proxy(),
+ make_scoped_ptr<Encryptor>(new FakeEncryptor)));
+}
+
+std::string GCMStoreImplTest::GetNextPersistentId() {
+ return base::Uint64ToString(next_persistent_id_++);
+}
+
+void GCMStoreImplTest::PumpLoop() { message_loop_.RunUntilIdle(); }
+
+void GCMStoreImplTest::LoadCallback(
+ scoped_ptr<GCMStore::LoadResult>* result_dst,
+ scoped_ptr<GCMStore::LoadResult> result) {
+ ASSERT_TRUE(result->success);
+ *result_dst = result.Pass();
+ run_loop_->Quit();
+ run_loop_.reset(new base::RunLoop());
+}
+
+void GCMStoreImplTest::UpdateCallback(bool success) {
+ ASSERT_EQ(expected_success_, success);
+}
+
+// Verify creating a new database and loading it.
+TEST_F(GCMStoreImplTest, LoadNew) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ EXPECT_EQ(0U, load_result->device_android_id);
+ EXPECT_EQ(0U, load_result->device_security_token);
+ EXPECT_TRUE(load_result->incoming_messages.empty());
+ EXPECT_TRUE(load_result->outgoing_messages.empty());
+ EXPECT_TRUE(load_result->gservices_settings.empty());
+ EXPECT_EQ(base::Time::FromInternalValue(0LL), load_result->last_checkin_time);
+}
+
+TEST_F(GCMStoreImplTest, DeviceCredentials) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ gcm_store->SetDeviceCredentials(
+ kDeviceId,
+ kDeviceToken,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(kDeviceId, load_result->device_android_id);
+ ASSERT_EQ(kDeviceToken, load_result->device_security_token);
+}
+
+TEST_F(GCMStoreImplTest, LastCheckinTime) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ base::Time last_checkin_time = base::Time::Now();
+
+ gcm_store->SetLastCheckinTime(
+ last_checkin_time,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+ ASSERT_EQ(last_checkin_time, load_result->last_checkin_time);
+}
+
+TEST_F(GCMStoreImplTest, GServicesSettings_ProtocolV2) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ std::map<std::string, std::string> settings;
+ settings["checkin_interval"] = "12345";
+ settings["mcs_port"] = "438";
+ settings["checkin_url"] = "http://checkin.google.com";
+ std::string digest = "digest1";
+
+ gcm_store->SetGServicesSettings(
+ settings,
+ digest,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(settings, load_result->gservices_settings);
+ ASSERT_EQ(digest, load_result->gservices_digest);
+
+ // Remove some, and add some.
+ settings.clear();
+ settings["checkin_interval"] = "54321";
+ settings["registration_url"] = "http://registration.google.com";
+ digest = "digest2";
+
+ gcm_store->SetGServicesSettings(
+ settings,
+ digest,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(settings, load_result->gservices_settings);
+ ASSERT_EQ(digest, load_result->gservices_digest);
+}
+
+TEST_F(GCMStoreImplTest, Registrations) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ // Add one registration with one sender.
+ linked_ptr<RegistrationInfo> registration1(new RegistrationInfo);
+ registration1->sender_ids.push_back("sender1");
+ registration1->registration_id = "registration1";
+ gcm_store->AddRegistration(
+ "app1",
+ registration1,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ // Add one registration with multiple senders.
+ linked_ptr<RegistrationInfo> registration2(new RegistrationInfo);
+ registration2->sender_ids.push_back("sender2_1");
+ registration2->sender_ids.push_back("sender2_2");
+ registration2->registration_id = "registration2";
+ gcm_store->AddRegistration(
+ "app2",
+ registration2,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(2u, load_result->registrations.size());
+ ASSERT_TRUE(load_result->registrations.find("app1") !=
+ load_result->registrations.end());
+ EXPECT_EQ(registration1->registration_id,
+ load_result->registrations["app1"]->registration_id);
+ ASSERT_EQ(1u, load_result->registrations["app1"]->sender_ids.size());
+ EXPECT_EQ(registration1->sender_ids[0],
+ load_result->registrations["app1"]->sender_ids[0]);
+ ASSERT_TRUE(load_result->registrations.find("app2") !=
+ load_result->registrations.end());
+ EXPECT_EQ(registration2->registration_id,
+ load_result->registrations["app2"]->registration_id);
+ ASSERT_EQ(2u, load_result->registrations["app2"]->sender_ids.size());
+ EXPECT_EQ(registration2->sender_ids[0],
+ load_result->registrations["app2"]->sender_ids[0]);
+ EXPECT_EQ(registration2->sender_ids[1],
+ load_result->registrations["app2"]->sender_ids[1]);
+}
+
+// Verify saving some incoming messages, reopening the directory, and then
+// removing those incoming messages.
+TEST_F(GCMStoreImplTest, IncomingMessages) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ std::vector<std::string> persistent_ids;
+ for (int i = 0; i < kNumPersistentIds; ++i) {
+ persistent_ids.push_back(GetNextPersistentId());
+ gcm_store->AddIncomingMessage(
+ persistent_ids.back(),
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+ }
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(persistent_ids, load_result->incoming_messages);
+ ASSERT_TRUE(load_result->outgoing_messages.empty());
+
+ gcm_store->RemoveIncomingMessages(
+ persistent_ids,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ load_result->incoming_messages.clear();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_TRUE(load_result->incoming_messages.empty());
+ ASSERT_TRUE(load_result->outgoing_messages.empty());
+}
+
+// Verify saving some outgoing messages, reopening the directory, and then
+// removing those outgoing messages.
+TEST_F(GCMStoreImplTest, OutgoingMessages) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ std::vector<std::string> persistent_ids;
+ const int kNumPersistentIds = 10;
+ for (int i = 0; i < kNumPersistentIds; ++i) {
+ persistent_ids.push_back(GetNextPersistentId());
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName + persistent_ids.back());
+ message.set_category(kCategoryName + persistent_ids.back());
+ gcm_store->AddOutgoingMessage(
+ persistent_ids.back(),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+ }
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_TRUE(load_result->incoming_messages.empty());
+ ASSERT_EQ(load_result->outgoing_messages.size(), persistent_ids.size());
+ for (int i = 0; i < kNumPersistentIds; ++i) {
+ std::string id = persistent_ids[i];
+ ASSERT_TRUE(load_result->outgoing_messages[id].get());
+ const mcs_proto::DataMessageStanza* message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(
+ load_result->outgoing_messages[id].get());
+ ASSERT_EQ(message->from(), kAppName + id);
+ ASSERT_EQ(message->category(), kCategoryName + id);
+ }
+
+ gcm_store->RemoveOutgoingMessages(
+ persistent_ids,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ load_result->outgoing_messages.clear();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_TRUE(load_result->incoming_messages.empty());
+ ASSERT_TRUE(load_result->outgoing_messages.empty());
+}
+
+// Verify incoming and outgoing messages don't conflict.
+TEST_F(GCMStoreImplTest, IncomingAndOutgoingMessages) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ std::vector<std::string> persistent_ids;
+ const int kNumPersistentIds = 10;
+ for (int i = 0; i < kNumPersistentIds; ++i) {
+ persistent_ids.push_back(GetNextPersistentId());
+ gcm_store->AddIncomingMessage(
+ persistent_ids.back(),
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName + persistent_ids.back());
+ message.set_category(kCategoryName + persistent_ids.back());
+ gcm_store->AddOutgoingMessage(
+ persistent_ids.back(),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+ }
+
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_EQ(persistent_ids, load_result->incoming_messages);
+ ASSERT_EQ(load_result->outgoing_messages.size(), persistent_ids.size());
+ for (int i = 0; i < kNumPersistentIds; ++i) {
+ std::string id = persistent_ids[i];
+ ASSERT_TRUE(load_result->outgoing_messages[id].get());
+ const mcs_proto::DataMessageStanza* message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(
+ load_result->outgoing_messages[id].get());
+ ASSERT_EQ(message->from(), kAppName + id);
+ ASSERT_EQ(message->category(), kCategoryName + id);
+ }
+
+ gcm_store->RemoveIncomingMessages(
+ persistent_ids,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+ gcm_store->RemoveOutgoingMessages(
+ persistent_ids,
+ base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
+ PumpLoop();
+
+ gcm_store = BuildGCMStore().Pass();
+ load_result->incoming_messages.clear();
+ load_result->outgoing_messages.clear();
+ gcm_store->Load(base::Bind(
+ &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
+ PumpLoop();
+
+ ASSERT_TRUE(load_result->incoming_messages.empty());
+ ASSERT_TRUE(load_result->outgoing_messages.empty());
+}
+
+// Test that per-app message limits are enforced, persisted across restarts,
+// and updated as messages are removed.
+TEST_F(GCMStoreImplTest, PerAppMessageLimits) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback,
+ base::Unretained(this),
+ &load_result));
+
+ // Add the initial (below app limit) messages.
+ for (int i = 0; i < kNumMessagesPerApp; ++i) {
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName);
+ message.set_category(kCategoryName);
+ EXPECT_TRUE(gcm_store->AddOutgoingMessage(
+ base::IntToString(i),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this))));
+ PumpLoop();
+ }
+
+ // Attempting to add some more should fail.
+ for (int i = 0; i < kNumMessagesPerApp; ++i) {
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName);
+ message.set_category(kCategoryName);
+ EXPECT_FALSE(gcm_store->AddOutgoingMessage(
+ base::IntToString(i + kNumMessagesPerApp),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this))));
+ PumpLoop();
+ }
+
+ // Tear down and restore the database.
+ gcm_store = BuildGCMStore().Pass();
+ gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback,
+ base::Unretained(this),
+ &load_result));
+ PumpLoop();
+
+ // Adding more messages should still fail.
+ for (int i = 0; i < kNumMessagesPerApp; ++i) {
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName);
+ message.set_category(kCategoryName);
+ EXPECT_FALSE(gcm_store->AddOutgoingMessage(
+ base::IntToString(i + kNumMessagesPerApp),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this))));
+ PumpLoop();
+ }
+
+ // Remove the existing messages.
+ for (int i = 0; i < kNumMessagesPerApp; ++i) {
+ gcm_store->RemoveOutgoingMessage(
+ base::IntToString(i),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this)));
+ PumpLoop();
+ }
+
+ // Successfully add new messages.
+ for (int i = 0; i < kNumMessagesPerApp; ++i) {
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName);
+ message.set_category(kCategoryName);
+ EXPECT_TRUE(gcm_store->AddOutgoingMessage(
+ base::IntToString(i + kNumMessagesPerApp),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this))));
+ PumpLoop();
+ }
+}
+
+// When the database is destroyed, all database updates should fail. At the
+// same time, they per-app message counts should not go up, as failures should
+// result in decrementing the counts.
+TEST_F(GCMStoreImplTest, AddMessageAfterDestroy) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback,
+ base::Unretained(this),
+ &load_result));
+ PumpLoop();
+ gcm_store->Destroy(base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this)));
+ PumpLoop();
+
+ expected_success_ = false;
+ for (int i = 0; i < kNumMessagesPerApp * 2; ++i) {
+ mcs_proto::DataMessageStanza message;
+ message.set_from(kAppName);
+ message.set_category(kCategoryName);
+ // Because all adds are failing, none should hit the per-app message limits.
+ EXPECT_TRUE(gcm_store->AddOutgoingMessage(
+ base::IntToString(i),
+ MCSMessage(message),
+ base::Bind(&GCMStoreImplTest::UpdateCallback,
+ base::Unretained(this))));
+ PumpLoop();
+ }
+}
+
+TEST_F(GCMStoreImplTest, ReloadAfterClose) {
+ scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
+ scoped_ptr<GCMStore::LoadResult> load_result;
+ gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback,
+ base::Unretained(this),
+ &load_result));
+ PumpLoop();
+
+ gcm_store->Close();
+ PumpLoop();
+
+ gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback,
+ base::Unretained(this),
+ &load_result));
+ PumpLoop();
+}
+
+} // namespace
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/gservices_settings.cc b/chromium/google_apis/gcm/engine/gservices_settings.cc
new file mode 100644
index 00000000000..4e989724b7e
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gservices_settings.cc
@@ -0,0 +1,342 @@
+// Copyright 2014 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 "google_apis/gcm/engine/gservices_settings.h"
+
+#include "base/bind.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+// The expected time in seconds between periodic checkins.
+const char kCheckinIntervalKey[] = "checkin_interval";
+// The override URL to the checkin server.
+const char kCheckinURLKey[] = "checkin_url";
+// The MCS machine name to connect to.
+const char kMCSHostnameKey[] = "gcm_hostname";
+// The MCS port to connect to.
+const char kMCSSecurePortKey[] = "gcm_secure_port";
+// The URL to get MCS registration IDs.
+const char kRegistrationURLKey[] = "gcm_registration_url";
+
+const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60; // seconds = 2 days.
+const int64 kMinimumCheckinInterval = 12 * 60 * 60; // seconds = 12 hours.
+const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin";
+const char kDefaultMCSHostname[] = "mtalk.google.com";
+const int kDefaultMCSMainSecurePort = 5228;
+const int kDefaultMCSFallbackSecurePort = 443;
+const char kDefaultRegistrationURL[] =
+ "https://android.clients.google.com/c2dm/register3";
+// Settings that are to be deleted are marked with this prefix in checkin
+// response.
+const char kDeleteSettingPrefix[] = "delete_";
+// Settings digest starts with verison number followed by '-'.
+const char kDigestVersionPrefix[] = "1-";
+const char kMCSEnpointTemplate[] = "https://%s:%d";
+const int kMaxSecurePort = 65535;
+
+std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) {
+ return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port);
+}
+
+// Default settings can be omitted, as GServicesSettings class provides
+// reasonable defaults.
+bool CanBeOmitted(const std::string& settings_name) {
+ return settings_name == kCheckinIntervalKey ||
+ settings_name == kCheckinURLKey ||
+ settings_name == kMCSHostnameKey ||
+ settings_name == kMCSSecurePortKey ||
+ settings_name == kRegistrationURLKey;
+}
+
+bool VerifyCheckinInterval(
+ const gcm::GServicesSettings::SettingsMap& settings) {
+ gcm::GServicesSettings::SettingsMap::const_iterator iter =
+ settings.find(kCheckinIntervalKey);
+ if (iter == settings.end())
+ return CanBeOmitted(kCheckinIntervalKey);
+
+ int64 checkin_interval = kMinimumCheckinInterval;
+ if (!base::StringToInt64(iter->second, &checkin_interval)) {
+ DVLOG(1) << "Failed to parse checkin interval: " << iter->second;
+ return false;
+ }
+ if (checkin_interval == std::numeric_limits<int64>::max()) {
+ DVLOG(1) << "Checkin interval is too big: " << checkin_interval;
+ return false;
+ }
+ if (checkin_interval < kMinimumCheckinInterval) {
+ DVLOG(1) << "Checkin interval: " << checkin_interval
+ << " is less than allowed minimum: " << kMinimumCheckinInterval;
+ }
+
+ return true;
+}
+
+bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) {
+ std::string mcs_hostname;
+ gcm::GServicesSettings::SettingsMap::const_iterator iter =
+ settings.find(kMCSHostnameKey);
+ if (iter == settings.end()) {
+ // Because endpoint has 2 parts (hostname and port) we are defaulting and
+ // moving on with verification.
+ if (CanBeOmitted(kMCSHostnameKey))
+ mcs_hostname = kDefaultMCSHostname;
+ else
+ return false;
+ } else if (iter->second.empty()) {
+ DVLOG(1) << "Empty MCS hostname provided.";
+ return false;
+ } else {
+ mcs_hostname = iter->second;
+ }
+
+ int mcs_secure_port = 0;
+ iter = settings.find(kMCSSecurePortKey);
+ if (iter == settings.end()) {
+ // Simlarly we might have to default the port, when only hostname is
+ // provided.
+ if (CanBeOmitted(kMCSSecurePortKey))
+ mcs_secure_port = kDefaultMCSMainSecurePort;
+ else
+ return false;
+ } else if (!base::StringToInt(iter->second, &mcs_secure_port)) {
+ DVLOG(1) << "Failed to parse MCS secure port: " << iter->second;
+ return false;
+ }
+
+ if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) {
+ DVLOG(1) << "Incorrect port value: " << mcs_secure_port;
+ return false;
+ }
+
+ GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
+ if (!mcs_main_endpoint.is_valid()) {
+ DVLOG(1) << "Invalid main MCS endpoint: "
+ << mcs_main_endpoint.possibly_invalid_spec();
+ return false;
+ }
+ GURL mcs_fallback_endpoint(
+ MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
+ if (!mcs_fallback_endpoint.is_valid()) {
+ DVLOG(1) << "Invalid fallback MCS endpoint: "
+ << mcs_fallback_endpoint.possibly_invalid_spec();
+ return false;
+ }
+
+ return true;
+}
+
+bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) {
+ gcm::GServicesSettings::SettingsMap::const_iterator iter =
+ settings.find(kCheckinURLKey);
+ if (iter == settings.end())
+ return CanBeOmitted(kCheckinURLKey);
+
+ GURL checkin_url(iter->second);
+ if (!checkin_url.is_valid()) {
+ DVLOG(1) << "Invalid checkin URL provided: " << iter->second;
+ return false;
+ }
+
+ return true;
+}
+
+bool VerifyRegistrationURL(
+ const gcm::GServicesSettings::SettingsMap& settings) {
+ gcm::GServicesSettings::SettingsMap::const_iterator iter =
+ settings.find(kRegistrationURLKey);
+ if (iter == settings.end())
+ return CanBeOmitted(kRegistrationURLKey);
+
+ GURL registration_url(iter->second);
+ if (!registration_url.is_valid()) {
+ DVLOG(1) << "Invalid registration URL provided: " << iter->second;
+ return false;
+ }
+
+ return true;
+}
+
+bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) {
+ return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) &&
+ VerifyCheckinURL(settings) && VerifyRegistrationURL(settings);
+}
+
+} // namespace
+
+namespace gcm {
+
+// static
+const base::TimeDelta GServicesSettings::MinimumCheckinInterval() {
+ return base::TimeDelta::FromSeconds(kMinimumCheckinInterval);
+}
+
+// static
+const GURL GServicesSettings::DefaultCheckinURL() {
+ return GURL(kDefaultCheckinURL);
+}
+
+// static
+std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) {
+ unsigned char hash[base::kSHA1Length];
+ std::string data;
+ for (SettingsMap::const_iterator iter = settings.begin();
+ iter != settings.end();
+ ++iter) {
+ data += iter->first;
+ data += '\0';
+ data += iter->second;
+ data += '\0';
+ }
+ base::SHA1HashBytes(
+ reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash);
+ std::string digest =
+ kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length);
+ digest = StringToLowerASCII(digest);
+ return digest;
+}
+
+GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) {
+ digest_ = CalculateDigest(settings_);
+}
+
+GServicesSettings::~GServicesSettings() {
+}
+
+bool GServicesSettings::UpdateFromCheckinResponse(
+ const checkin_proto::AndroidCheckinResponse& checkin_response) {
+ if (!checkin_response.has_settings_diff()) {
+ DVLOG(1) << "Field settings_diff not set in response.";
+ return false;
+ }
+
+ bool settings_diff = checkin_response.settings_diff();
+ SettingsMap new_settings;
+ // Only reuse the existing settings, if we are given a settings difference.
+ if (settings_diff)
+ new_settings = settings_map();
+
+ for (int i = 0; i < checkin_response.setting_size(); ++i) {
+ std::string name = checkin_response.setting(i).name();
+ if (name.empty()) {
+ DVLOG(1) << "Setting name is empty";
+ return false;
+ }
+
+ if (settings_diff && name.find(kDeleteSettingPrefix) == 0) {
+ std::string setting_to_delete =
+ name.substr(arraysize(kDeleteSettingPrefix) - 1);
+ new_settings.erase(setting_to_delete);
+ DVLOG(1) << "Setting deleted: " << setting_to_delete;
+ } else {
+ std::string value = checkin_response.setting(i).value();
+ new_settings[name] = value;
+ DVLOG(1) << "New setting: '" << name << "' : '" << value << "'";
+ }
+ }
+
+ if (!VerifySettings(new_settings))
+ return false;
+
+ settings_.swap(new_settings);
+ digest_ = CalculateDigest(settings_);
+ return true;
+}
+
+void GServicesSettings::UpdateFromLoadResult(
+ const GCMStore::LoadResult& load_result) {
+ // No need to try to update settings when load_result is empty.
+ if (load_result.gservices_settings.empty())
+ return;
+ if (!VerifySettings(load_result.gservices_settings))
+ return;
+ std::string digest = CalculateDigest(load_result.gservices_settings);
+ if (digest != load_result.gservices_digest) {
+ DVLOG(1) << "G-services settings digest mismatch. "
+ << "Expected digest: " << load_result.gservices_digest
+ << ". Calculated digest is: " << digest;
+ return;
+ }
+
+ settings_ = load_result.gservices_settings;
+ digest_ = load_result.gservices_digest;
+}
+
+base::TimeDelta GServicesSettings::GetCheckinInterval() const {
+ int64 checkin_interval = kMinimumCheckinInterval;
+ SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey);
+ if (iter == settings_.end() ||
+ !base::StringToInt64(iter->second, &checkin_interval)) {
+ checkin_interval = kDefaultCheckinInterval;
+ }
+
+ if (checkin_interval < kMinimumCheckinInterval)
+ checkin_interval = kMinimumCheckinInterval;
+
+ return base::TimeDelta::FromSeconds(checkin_interval);
+}
+
+GURL GServicesSettings::GetCheckinURL() const {
+ SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey);
+ if (iter == settings_.end() || iter->second.empty())
+ return GURL(kDefaultCheckinURL);
+ return GURL(iter->second);
+}
+
+GURL GServicesSettings::GetMCSMainEndpoint() const {
+ // Get alternative hostname or use default.
+ std::string mcs_hostname;
+ SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
+ if (iter != settings_.end() && !iter->second.empty())
+ mcs_hostname = iter->second;
+ else
+ mcs_hostname = kDefaultMCSHostname;
+
+ // Get alternative secure port or use defualt.
+ int mcs_secure_port = 0;
+ iter = settings_.find(kMCSSecurePortKey);
+ if (iter == settings_.end() || iter->second.empty() ||
+ !base::StringToInt(iter->second, &mcs_secure_port)) {
+ mcs_secure_port = kDefaultMCSMainSecurePort;
+ }
+
+ // If constructed address makes sense use it.
+ GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
+ if (mcs_endpoint.is_valid())
+ return mcs_endpoint;
+
+ // Otherwise use default settings.
+ return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort));
+}
+
+GURL GServicesSettings::GetMCSFallbackEndpoint() const {
+ // Get alternative hostname or use default.
+ std::string mcs_hostname;
+ SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
+ if (iter != settings_.end() && !iter->second.empty())
+ mcs_hostname = iter->second;
+ else
+ mcs_hostname = kDefaultMCSHostname;
+
+ // If constructed address makes sense use it.
+ GURL mcs_endpoint(
+ MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
+ if (mcs_endpoint.is_valid())
+ return mcs_endpoint;
+
+ return GURL(
+ MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort));
+}
+
+GURL GServicesSettings::GetRegistrationURL() const {
+ SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey);
+ if (iter == settings_.end() || iter->second.empty())
+ return GURL(kDefaultRegistrationURL);
+ return GURL(iter->second);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/gservices_settings.h b/chromium/google_apis/gcm/engine/gservices_settings.h
new file mode 100644
index 00000000000..d3aeeb03d29
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gservices_settings.h
@@ -0,0 +1,82 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_GSERVICES_SETTINGS_H_
+#define GOOGLE_APIS_GCM_ENGINE_GSERVICES_SETTINGS_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "google_apis/gcm/engine/gcm_store.h"
+#include "google_apis/gcm/protocol/checkin.pb.h"
+#include "url/gurl.h"
+
+namespace gcm {
+
+// Class responsible for handling G-services settings. It takes care of
+// extracting them from checkin response and storing in GCMStore.
+class GCM_EXPORT GServicesSettings {
+ public:
+ typedef std::map<std::string, std::string> SettingsMap;
+
+ // Minimum periodic checkin interval in seconds.
+ static const base::TimeDelta MinimumCheckinInterval();
+
+ // Default checkin URL.
+ static const GURL DefaultCheckinURL();
+
+ // Calculates digest of provided settings.
+ static std::string CalculateDigest(const SettingsMap& settings);
+
+ GServicesSettings();
+ ~GServicesSettings();
+
+ // Updates the settings based on |checkin_response|.
+ bool UpdateFromCheckinResponse(
+ const checkin_proto::AndroidCheckinResponse& checkin_response);
+
+ // Updates the settings based on |load_result|. Returns true if update was
+ // successful, false otherwise.
+ void UpdateFromLoadResult(const GCMStore::LoadResult& load_result);
+
+ SettingsMap settings_map() const { return settings_; }
+
+ std::string digest() const { return digest_; }
+
+ // Gets the interval at which device should perform a checkin.
+ base::TimeDelta GetCheckinInterval() const;
+
+ // Gets the URL to use when checking in.
+ GURL GetCheckinURL() const;
+
+ // Gets address of main MCS endpoint.
+ GURL GetMCSMainEndpoint() const;
+
+ // Gets address of fallback MCS endpoint.
+ GURL GetMCSFallbackEndpoint() const;
+
+ // Gets the URL to use when registering or unregistering the apps.
+ GURL GetRegistrationURL() const;
+
+ private:
+ // Digest (hash) of the settings, used to check whether settings need update.
+ // It is meant to be sent with checkin request, instead of sending the whole
+ // settings table.
+ std::string digest_;
+
+ // G-services settings as provided by checkin response.
+ SettingsMap settings_;
+
+ // Factory for creating references in callbacks.
+ base::WeakPtrFactory<GServicesSettings> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GServicesSettings);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_GSERVICES_SETTINGS_H_
diff --git a/chromium/google_apis/gcm/engine/gservices_settings_unittest.cc b/chromium/google_apis/gcm/engine/gservices_settings_unittest.cc
new file mode 100644
index 00000000000..9e02109200c
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/gservices_settings_unittest.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 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 "base/strings/string_number_conversions.h"
+#include "google_apis/gcm/engine/gservices_settings.h"
+#include "google_apis/gcm/engine/registration_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+const int64 kAlternativeCheckinInterval = 16 * 60 * 60;
+const char kAlternativeCheckinURL[] = "http://alternative.url/checkin";
+const char kAlternativeMCSHostname[] = "alternative.gcm.host";
+const int kAlternativeMCSSecurePort = 7777;
+const char kAlternativeRegistrationURL[] =
+ "http://alternative.url/registration";
+
+const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60; // seconds = 2 days.
+const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin";
+const char kDefaultRegistrationURL[] =
+ "https://android.clients.google.com/c2dm/register3";
+const char kDefaultSettingsDigest[] =
+ "1-da39a3ee5e6b4b0d3255bfef95601890afd80709";
+const char kAlternativeSettingsDigest[] =
+ "1-7da4aa4eb38a8bd3e330e3751cc0899924499134";
+
+void AddSettingsToResponse(
+ checkin_proto::AndroidCheckinResponse& checkin_response,
+ const GServicesSettings::SettingsMap& settings,
+ bool settings_diff) {
+ for (GServicesSettings::SettingsMap::const_iterator iter = settings.begin();
+ iter != settings.end();
+ ++iter) {
+ checkin_proto::GservicesSetting* setting = checkin_response.add_setting();
+ setting->set_name(iter->first);
+ setting->set_value(iter->second);
+ }
+ checkin_response.set_settings_diff(settings_diff);
+}
+
+} // namespace
+
+class GServicesSettingsTest : public testing::Test {
+ public:
+ GServicesSettingsTest();
+ virtual ~GServicesSettingsTest();
+
+ void CheckAllSetToDefault();
+
+ GServicesSettings& settings() {
+ return gserivces_settings_;
+ }
+
+ private:
+ GServicesSettings gserivces_settings_;
+};
+
+GServicesSettingsTest::GServicesSettingsTest()
+ : gserivces_settings_() {
+}
+
+GServicesSettingsTest::~GServicesSettingsTest() {}
+
+void GServicesSettingsTest::CheckAllSetToDefault() {
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL(kDefaultCheckinURL), settings().GetCheckinURL());
+ EXPECT_EQ(GURL("https://mtalk.google.com:5228"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://mtalk.google.com:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kDefaultRegistrationURL), settings().GetRegistrationURL());
+}
+
+// Verifies default values of the G-services settings and settings digest.
+TEST_F(GServicesSettingsTest, DefaultSettingsAndDigest) {
+ CheckAllSetToDefault();
+ EXPECT_EQ(kDefaultSettingsDigest, settings().digest());
+ EXPECT_EQ(kDefaultSettingsDigest,
+ GServicesSettings::CalculateDigest(settings().settings_map()));
+}
+
+// Verifies digest calculation for the sample provided by protocol owners.
+TEST_F(GServicesSettingsTest, CalculateDigest) {
+ GServicesSettings::SettingsMap settings_map;
+ settings_map["android_id"] = "55XXXXXXXXXXXXXXXX0";
+ settings_map["checkin_interval"] = "86400";
+ settings_map["checkin_url"] =
+ "https://fake.address.google.com/canary/checkin";
+ settings_map["chrome_device"] = "1";
+ settings_map["device_country"] = "us";
+ settings_map["gcm_hostname"] = "fake.address.google.com";
+ settings_map["gcm_secure_port"] = "443";
+
+ EXPECT_EQ("1-33381ccd1cf5791dc0e6dfa234266fa9f1259197",
+ GServicesSettings::CalculateDigest(settings_map));
+}
+
+// Verifies that settings are not updated when load result is empty.
+TEST_F(GServicesSettingsTest, UpdateFromEmptyLoadResult) {
+ GCMStore::LoadResult result;
+ result.gservices_digest = "";
+ settings().UpdateFromLoadResult(result);
+
+ CheckAllSetToDefault();
+ EXPECT_EQ(kDefaultSettingsDigest, settings().digest());
+}
+
+// Verifies that settings are not when digest value does not match.
+TEST_F(GServicesSettingsTest, UpdateFromLoadResultWithSettingMissing) {
+ GCMStore::LoadResult result;
+ result.gservices_settings["checkin_internval"] = "100000";
+ result.gservices_digest = "digest_value";
+ settings().UpdateFromLoadResult(result);
+
+ CheckAllSetToDefault();
+ EXPECT_EQ(kDefaultSettingsDigest, settings().digest());
+}
+
+// Verifies that the settings are set correctly based on the load result.
+TEST_F(GServicesSettingsTest, UpdateFromLoadResult) {
+ GCMStore::LoadResult result;
+ result.gservices_settings["checkin_interval"] =
+ base::Int64ToString(kAlternativeCheckinInterval);
+ result.gservices_settings["checkin_url"] = kAlternativeCheckinURL;
+ result.gservices_settings["gcm_hostname"] = kAlternativeMCSHostname;
+ result.gservices_settings["gcm_secure_port"] =
+ base::IntToString(kAlternativeMCSSecurePort);
+ result.gservices_settings["gcm_registration_url"] =
+ kAlternativeRegistrationURL;
+ result.gservices_digest = kAlternativeSettingsDigest;
+ settings().UpdateFromLoadResult(result);
+
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kAlternativeCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL(kAlternativeCheckinURL), settings().GetCheckinURL());
+ EXPECT_EQ(GURL("https://alternative.gcm.host:7777"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://alternative.gcm.host:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kAlternativeRegistrationURL), settings().GetRegistrationURL());
+ EXPECT_EQ(GServicesSettings::CalculateDigest(result.gservices_settings),
+ settings().digest());
+}
+
+// Verifies that the checkin interval is updated to minimum if the original
+// value is less than minimum.
+TEST_F(GServicesSettingsTest, CheckinResponseMinimumCheckinInterval) {
+ // Setting the checkin interval to less than minimum.
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["checkin_interval"] = "3600";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ EXPECT_EQ(GServicesSettings::MinimumCheckinInterval(),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Verifies that default checkin interval can be selectively overwritten.
+TEST_F(GServicesSettingsTest, CheckinResponseUpdateCheckinInterval) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["checkin_interval"] = "86400";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ // Only the checkin interval was updated:
+ EXPECT_EQ(base::TimeDelta::FromSeconds(86400),
+ settings().GetCheckinInterval());
+
+ // Other settings still set to default.
+ EXPECT_EQ(GURL("https://mtalk.google.com:5228"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://mtalk.google.com:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kDefaultCheckinURL), settings().GetCheckinURL());
+ EXPECT_EQ(GURL(kDefaultRegistrationURL), settings().GetRegistrationURL());
+
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Verifies that default registration URL can be selectively overwritten.
+TEST_F(GServicesSettingsTest, CheckinResponseUpdateRegistrationURL) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["gcm_registration_url"] = "https://new.registration.url";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ // Only the registration URL was updated:
+ EXPECT_EQ(GURL("https://new.registration.url"),
+ settings().GetRegistrationURL());
+
+ // Other settings still set to default.
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL("https://mtalk.google.com:5228"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://mtalk.google.com:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kDefaultCheckinURL), settings().GetCheckinURL());
+
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Verifies that default checkin URL can be selectively overwritten.
+TEST_F(GServicesSettingsTest, CheckinResponseUpdateCheckinURL) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["checkin_url"] = "https://new.checkin.url";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ // Only the checkin URL was updated:
+ EXPECT_EQ(GURL("https://new.checkin.url"), settings().GetCheckinURL());
+
+ // Other settings still set to default.
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL("https://mtalk.google.com:5228"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://mtalk.google.com:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kDefaultRegistrationURL), settings().GetRegistrationURL());
+
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Verifies that default MCS hostname can be selectively overwritten.
+TEST_F(GServicesSettingsTest, CheckinResponseUpdateMCSHostname) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["gcm_hostname"] = "new.gcm.hostname";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ // Only the MCS endpoints were updated:
+ EXPECT_EQ(GURL("https://new.gcm.hostname:5228"),
+ settings().GetMCSMainEndpoint());
+ EXPECT_EQ(GURL("https://new.gcm.hostname:443"),
+ settings().GetMCSFallbackEndpoint());
+
+ // Other settings still set to default.
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL(kDefaultCheckinURL), settings().GetCheckinURL());
+ EXPECT_EQ(GURL(kDefaultRegistrationURL), settings().GetRegistrationURL());
+
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Verifies that default MCS secure port can be selectively overwritten.
+TEST_F(GServicesSettingsTest, CheckinResponseUpdateMCSSecurePort) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+ GServicesSettings::SettingsMap new_settings;
+ new_settings["gcm_secure_port"] = "5229";
+ AddSettingsToResponse(checkin_response, new_settings, false);
+
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+
+ // Only the main MCS endpoint was updated:
+ EXPECT_EQ(GURL("https://mtalk.google.com:5229"),
+ settings().GetMCSMainEndpoint());
+
+ // Other settings still set to default.
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultCheckinInterval),
+ settings().GetCheckinInterval());
+ EXPECT_EQ(GURL(kDefaultCheckinURL), settings().GetCheckinURL());
+ EXPECT_EQ(GURL("https://mtalk.google.com:443"),
+ settings().GetMCSFallbackEndpoint());
+ EXPECT_EQ(GURL(kDefaultRegistrationURL), settings().GetRegistrationURL());
+
+ EXPECT_EQ(GServicesSettings::CalculateDigest(new_settings),
+ settings().digest());
+}
+
+// Update from checkin response should also do incremental update for both cases
+// where some settings are removed or added.
+TEST_F(GServicesSettingsTest, UpdateFromCheckinResponseSettingsDiff) {
+ checkin_proto::AndroidCheckinResponse checkin_response;
+
+ // Only the new settings will be included in the response with settings diff.
+ GServicesSettings::SettingsMap settings_diff;
+ settings_diff["new_setting_1"] = "new_setting_1_value";
+ settings_diff["new_setting_2"] = "new_setting_2_value";
+ settings_diff["gcm_secure_port"] = "5229";
+
+ // Full settings are necessary to calculate digest.
+ GServicesSettings::SettingsMap full_settings(settings_diff);
+ std::string digest = GServicesSettings::CalculateDigest(full_settings);
+
+ checkin_response.Clear();
+ AddSettingsToResponse(checkin_response, settings_diff, true);
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+ EXPECT_EQ(full_settings, settings().settings_map());
+ // Default setting overwritten by settings diff.
+ EXPECT_EQ(GURL("https://mtalk.google.com:5229"),
+ settings().GetMCSMainEndpoint());
+
+ // Setting up diff removing some of the values (including default setting).
+ settings_diff.clear();
+ settings_diff["delete_new_setting_1"] = "";
+ settings_diff["delete_gcm_secure_port"] = "";
+ settings_diff["new_setting_3"] = "new_setting_3_value";
+
+ // Updating full settings to calculate digest.
+ full_settings.erase(full_settings.find("new_setting_1"));
+ full_settings.erase(full_settings.find("gcm_secure_port"));
+ full_settings["new_setting_3"] = "new_setting_3_value";
+ digest = GServicesSettings::CalculateDigest(full_settings);
+
+ checkin_response.Clear();
+ AddSettingsToResponse(checkin_response, settings_diff, true);
+ EXPECT_TRUE(settings().UpdateFromCheckinResponse(checkin_response));
+ EXPECT_EQ(full_settings, settings().settings_map());
+ // Default setting back to norm.
+ EXPECT_EQ(GURL("https://mtalk.google.com:5228"),
+ settings().GetMCSMainEndpoint());
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/heartbeat_manager.cc b/chromium/google_apis/gcm/engine/heartbeat_manager.cc
new file mode 100644
index 00000000000..5b5ae476a4b
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/heartbeat_manager.cc
@@ -0,0 +1,119 @@
+// Copyright 2014 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 "google_apis/gcm/engine/heartbeat_manager.h"
+
+#include "google_apis/gcm/protocol/mcs.pb.h"
+#include "net/base/network_change_notifier.h"
+
+namespace gcm {
+
+namespace {
+// The default heartbeat when on a mobile or unknown network .
+const int64 kCellHeartbeatDefaultMs = 1000 * 60 * 28; // 28 minutes.
+// The default heartbeat when on WiFi (also used for ethernet).
+const int64 kWifiHeartbeatDefaultMs = 1000 * 60 * 15; // 15 minutes.
+// The default heartbeat ack interval.
+const int64 kHeartbeatAckDefaultMs = 1000 * 60 * 1; // 1 minute.
+} // namespace
+
+HeartbeatManager::HeartbeatManager()
+ : waiting_for_ack_(false),
+ heartbeat_interval_ms_(0),
+ server_interval_ms_(0),
+ heartbeat_timer_(true /* retain user task */,
+ false /* not repeating */),
+ weak_ptr_factory_(this) {}
+
+HeartbeatManager::~HeartbeatManager() {}
+
+void HeartbeatManager::Start(
+ const base::Closure& send_heartbeat_callback,
+ const base::Closure& trigger_reconnect_callback) {
+ DCHECK(!send_heartbeat_callback.is_null());
+ DCHECK(!trigger_reconnect_callback.is_null());
+ send_heartbeat_callback_ = send_heartbeat_callback;
+ trigger_reconnect_callback_ = trigger_reconnect_callback;
+
+ // Kicks off the timer.
+ waiting_for_ack_ = false;
+ RestartTimer();
+}
+
+void HeartbeatManager::Stop() {
+ heartbeat_timer_.Stop();
+ waiting_for_ack_ = false;
+}
+
+void HeartbeatManager::OnHeartbeatAcked() {
+ if (!heartbeat_timer_.IsRunning())
+ return;
+
+ DCHECK(!send_heartbeat_callback_.is_null());
+ DCHECK(!trigger_reconnect_callback_.is_null());
+ waiting_for_ack_ = false;
+ RestartTimer();
+}
+
+void HeartbeatManager::UpdateHeartbeatConfig(
+ const mcs_proto::HeartbeatConfig& config) {
+ if (!config.IsInitialized() ||
+ !config.has_interval_ms() ||
+ config.interval_ms() <= 0) {
+ return;
+ }
+ DVLOG(1) << "Updating heartbeat interval to " << config.interval_ms();
+ server_interval_ms_ = config.interval_ms();
+}
+
+base::TimeTicks HeartbeatManager::GetNextHeartbeatTime() const {
+ if (heartbeat_timer_.IsRunning())
+ return heartbeat_timer_.desired_run_time();
+ else
+ return base::TimeTicks();
+}
+
+void HeartbeatManager::OnHeartbeatTriggered() {
+ if (waiting_for_ack_) {
+ LOG(WARNING) << "Lost connection to MCS, reconnecting.";
+ Stop();
+ trigger_reconnect_callback_.Run();
+ return;
+ }
+
+ waiting_for_ack_ = true;
+ RestartTimer();
+ send_heartbeat_callback_.Run();
+}
+
+void HeartbeatManager::RestartTimer() {
+ if (!waiting_for_ack_) {
+ // Recalculate the timer interval based network type.
+ if (server_interval_ms_ != 0) {
+ // If a server interval is set, it overrides any local one.
+ heartbeat_interval_ms_ = server_interval_ms_;
+ } else if (net::NetworkChangeNotifier::GetConnectionType() ==
+ net::NetworkChangeNotifier::CONNECTION_WIFI ||
+ net::NetworkChangeNotifier::GetConnectionType() ==
+ net::NetworkChangeNotifier::CONNECTION_ETHERNET) {
+ heartbeat_interval_ms_ = kWifiHeartbeatDefaultMs;
+ } else {
+ // For unknown connections, use the longer cellular heartbeat interval.
+ heartbeat_interval_ms_ = kCellHeartbeatDefaultMs;
+ }
+ DVLOG(1) << "Sending next heartbeat in "
+ << heartbeat_interval_ms_ << " ms.";
+ } else {
+ heartbeat_interval_ms_ = kHeartbeatAckDefaultMs;
+ DVLOG(1) << "Resetting timer for ack with "
+ << heartbeat_interval_ms_ << " ms interval.";
+ }
+ heartbeat_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(
+ heartbeat_interval_ms_),
+ base::Bind(&HeartbeatManager::OnHeartbeatTriggered,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/heartbeat_manager.h b/chromium/google_apis/gcm/engine/heartbeat_manager.h
new file mode 100644
index 00000000000..9266b98c8d4
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/heartbeat_manager.h
@@ -0,0 +1,82 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_HEARTBEAT_MANAGER_H_
+#define GOOGLE_APIS_GCM_ENGINE_HEARTBEAT_MANAGER_H_
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "google_apis/gcm/base/gcm_export.h"
+
+namespace mcs_proto {
+class HeartbeatConfig;
+}
+
+namespace gcm {
+
+// A heartbeat management class, capable of sending and handling heartbeat
+// receipt/failures and triggering reconnection as necessary.
+class GCM_EXPORT HeartbeatManager {
+ public:
+ HeartbeatManager();
+ ~HeartbeatManager();
+
+ // Start the heartbeat logic.
+ // |send_heartbeat_callback_| is the callback the HeartbeatManager uses to
+ // send new heartbeats. Only one heartbeat can be outstanding at a time.
+ void Start(const base::Closure& send_heartbeat_callback,
+ const base::Closure& trigger_reconnect_callback);
+
+ // Stop the timer. Start(..) must be called again to begin sending heartbeats
+ // afterwards.
+ void Stop();
+
+ // Reset the heartbeat timer. It is valid to call this even if no heartbeat
+ // is associated with the ack (for example if another signal is used to
+ // determine that the connection is alive).
+ void OnHeartbeatAcked();
+
+ // Updates the current heartbeat interval.
+ void UpdateHeartbeatConfig(const mcs_proto::HeartbeatConfig& config);
+
+ // Returns the next scheduled heartbeat time. A null time means
+ // no heartbeat is pending. If non-null and less than the
+ // current time (in ticks), the heartbeat has been triggered and an ack is
+ // pending.
+ base::TimeTicks GetNextHeartbeatTime() const;
+
+ protected:
+ // Helper method to send heartbeat on timer trigger.
+ void OnHeartbeatTriggered();
+
+ private:
+ // Restarts the heartbeat timer.
+ void RestartTimer();
+
+ // Whether the last heartbeat ping sent has been acknowledged or not.
+ bool waiting_for_ack_;
+
+ // The current heartbeat interval.
+ int heartbeat_interval_ms_;
+ // The most recent server-provided heartbeat interval (0 if none has been
+ // provided).
+ int server_interval_ms_;
+
+ // Timer for triggering heartbeats.
+ base::Timer heartbeat_timer_;
+
+ // Callbacks for interacting with the the connection.
+ base::Closure send_heartbeat_callback_;
+ base::Closure trigger_reconnect_callback_;
+
+ base::WeakPtrFactory<HeartbeatManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeartbeatManager);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_HEARTBEAT_MANAGER_H_
diff --git a/chromium/google_apis/gcm/engine/heartbeat_manager_unittest.cc b/chromium/google_apis/gcm/engine/heartbeat_manager_unittest.cc
new file mode 100644
index 00000000000..2c3b668d533
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/heartbeat_manager_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright 2014 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 "google_apis/gcm/engine/heartbeat_manager.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/protocol/mcs.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+mcs_proto::HeartbeatConfig BuildHeartbeatConfig(int interval_ms) {
+ mcs_proto::HeartbeatConfig config;
+ config.set_interval_ms(interval_ms);
+ return config;
+}
+
+class TestHeartbeatManager : public HeartbeatManager {
+ public:
+ TestHeartbeatManager() {}
+ virtual ~TestHeartbeatManager() {}
+
+ // Bypass the heartbeat timer, and send the heartbeat now.
+ void TriggerHearbeat();
+};
+
+void TestHeartbeatManager::TriggerHearbeat() {
+ OnHeartbeatTriggered();
+}
+
+class HeartbeatManagerTest : public testing::Test {
+ public:
+ HeartbeatManagerTest();
+ virtual ~HeartbeatManagerTest() {}
+
+ TestHeartbeatManager* manager() const { return manager_.get(); }
+ int heartbeats_sent() const { return heartbeats_sent_; }
+ int reconnects_triggered() const { return reconnects_triggered_; }
+
+ // Starts the heartbeat manager.
+ void StartManager();
+
+ private:
+ // Helper functions for verifying heartbeat manager effects.
+ void SendHeartbeatClosure();
+ void TriggerReconnectClosure();
+
+ scoped_ptr<TestHeartbeatManager> manager_;
+
+ int heartbeats_sent_;
+ int reconnects_triggered_;
+
+ base::MessageLoop message_loop_;
+};
+
+HeartbeatManagerTest::HeartbeatManagerTest()
+ : manager_(new TestHeartbeatManager()),
+ heartbeats_sent_(0),
+ reconnects_triggered_(0) {
+}
+
+void HeartbeatManagerTest::StartManager() {
+ manager_->Start(base::Bind(&HeartbeatManagerTest::SendHeartbeatClosure,
+ base::Unretained(this)),
+ base::Bind(&HeartbeatManagerTest::TriggerReconnectClosure,
+ base::Unretained(this)));
+}
+
+void HeartbeatManagerTest::SendHeartbeatClosure() {
+ heartbeats_sent_++;
+}
+
+void HeartbeatManagerTest::TriggerReconnectClosure() {
+ reconnects_triggered_++;
+}
+
+// Basic initialization. No heartbeat should be pending.
+TEST_F(HeartbeatManagerTest, Init) {
+ EXPECT_TRUE(manager()->GetNextHeartbeatTime().is_null());
+}
+
+// Acknowledging a heartbeat before starting the manager should have no effect.
+TEST_F(HeartbeatManagerTest, AckBeforeStart) {
+ manager()->OnHeartbeatAcked();
+ EXPECT_TRUE(manager()->GetNextHeartbeatTime().is_null());
+}
+
+// Starting the manager should start the heartbeat timer.
+TEST_F(HeartbeatManagerTest, Start) {
+ StartManager();
+ EXPECT_GT(manager()->GetNextHeartbeatTime(), base::TimeTicks::Now());
+ EXPECT_EQ(0, heartbeats_sent());
+ EXPECT_EQ(0, reconnects_triggered());
+}
+
+// Acking the heartbeat should trigger a new heartbeat timer.
+TEST_F(HeartbeatManagerTest, AckedHeartbeat) {
+ StartManager();
+ manager()->TriggerHearbeat();
+ base::TimeTicks heartbeat = manager()->GetNextHeartbeatTime();
+ EXPECT_GT(heartbeat, base::TimeTicks::Now());
+ EXPECT_EQ(1, heartbeats_sent());
+ EXPECT_EQ(0, reconnects_triggered());
+
+ manager()->OnHeartbeatAcked();
+ EXPECT_LT(heartbeat, manager()->GetNextHeartbeatTime());
+ EXPECT_EQ(1, heartbeats_sent());
+ EXPECT_EQ(0, reconnects_triggered());
+
+ manager()->TriggerHearbeat();
+ EXPECT_EQ(2, heartbeats_sent());
+ EXPECT_EQ(0, reconnects_triggered());
+}
+
+// Trigger a heartbeat when one was outstanding should reset the connection.
+TEST_F(HeartbeatManagerTest, UnackedHeartbeat) {
+ StartManager();
+ manager()->TriggerHearbeat();
+ EXPECT_EQ(1, heartbeats_sent());
+ EXPECT_EQ(0, reconnects_triggered());
+
+ manager()->TriggerHearbeat();
+ EXPECT_EQ(1, heartbeats_sent());
+ EXPECT_EQ(1, reconnects_triggered());
+}
+
+// Updating the heartbeat interval before starting should result in the new
+// interval being used at Start time.
+TEST_F(HeartbeatManagerTest, UpdateIntervalThenStart) {
+ const int kIntervalMs = 60 * 1000; // 60 seconds.
+ manager()->UpdateHeartbeatConfig(BuildHeartbeatConfig(kIntervalMs));
+ EXPECT_TRUE(manager()->GetNextHeartbeatTime().is_null());
+ StartManager();
+ EXPECT_LE(manager()->GetNextHeartbeatTime() - base::TimeTicks::Now(),
+ base::TimeDelta::FromMilliseconds(kIntervalMs));
+}
+
+// Updating the heartbeat interval after starting should only use the new
+// interval on the next heartbeat.
+TEST_F(HeartbeatManagerTest, StartThenUpdateInterval) {
+ const int kIntervalMs = 60 * 1000; // 60 seconds.
+ StartManager();
+ base::TimeTicks heartbeat = manager()->GetNextHeartbeatTime();
+ EXPECT_GT(heartbeat - base::TimeTicks::Now(),
+ base::TimeDelta::FromMilliseconds(kIntervalMs));
+
+ // Updating the interval should not affect an outstanding heartbeat.
+ manager()->UpdateHeartbeatConfig(BuildHeartbeatConfig(kIntervalMs));
+ EXPECT_EQ(heartbeat, manager()->GetNextHeartbeatTime());
+
+ // Triggering and acking the heartbeat should result in a heartbeat being
+ // posted with the new interval.
+ manager()->TriggerHearbeat();
+ manager()->OnHeartbeatAcked();
+
+ EXPECT_LE(manager()->GetNextHeartbeatTime() - base::TimeTicks::Now(),
+ base::TimeDelta::FromMilliseconds(kIntervalMs));
+ EXPECT_NE(heartbeat, manager()->GetNextHeartbeatTime());
+}
+
+// Stopping the manager should reset the heartbeat timer.
+TEST_F(HeartbeatManagerTest, Stop) {
+ StartManager();
+ EXPECT_GT(manager()->GetNextHeartbeatTime(), base::TimeTicks::Now());
+
+ manager()->Stop();
+ EXPECT_TRUE(manager()->GetNextHeartbeatTime().is_null());
+}
+
+} // namespace
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/mcs_client.cc b/chromium/google_apis/gcm/engine/mcs_client.cc
index f0af051379f..a99a333a37e 100644
--- a/chromium/google_apis/gcm/engine/mcs_client.cc
+++ b/chromium/google_apis/gcm/engine/mcs_client.cc
@@ -4,13 +4,18 @@
#include "google_apis/gcm/engine/mcs_client.h"
+#include <set>
+
#include "base/basictypes.h"
#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
#include "google_apis/gcm/base/mcs_util.h"
#include "google_apis/gcm/base/socket_stream.h"
#include "google_apis/gcm/engine/connection_factory.h"
-#include "google_apis/gcm/engine/rmq_store.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
using namespace google::protobuf::io;
@@ -20,9 +25,6 @@ namespace {
typedef scoped_ptr<google::protobuf::MessageLite> MCSProto;
-// TODO(zea): get these values from MCS settings.
-const int64 kHeartbeatDefaultSeconds = 60 * 15; // 15 minutes.
-
// The category of messages intended for the GCM client itself from MCS.
const char kMCSCategory[] = "com.google.android.gsf.gtalkservice";
@@ -30,8 +32,8 @@ const char kMCSCategory[] = "com.google.android.gsf.gtalkservice";
const char kGCMFromField[] = "gcm@android.com";
// MCS status message types.
+// TODO(zea): handle these at the GCMClient layer.
const char kIdleNotification[] = "IdleNotification";
-// TODO(zea): consume the following message types:
// const char kAlwaysShowOnIdle[] = "ShowAwayOnIdle";
// const char kPowerNotification[] = "PowerNotification";
// const char kDataActiveNotification[] = "DataActiveNotification";
@@ -64,6 +66,47 @@ bool BuildPersistentIdListFromProto(const google::protobuf::string& bytes,
} // namespace
+class CollapseKey {
+ public:
+ explicit CollapseKey(const mcs_proto::DataMessageStanza& message);
+ ~CollapseKey();
+
+ // Comparison operator for use in maps.
+ bool operator<(const CollapseKey& right) const;
+
+ // Whether the message had a valid collapse key.
+ bool IsValid() const;
+
+ std::string token() const { return token_; }
+ std::string app_id() const { return app_id_; }
+ int64 device_user_id() const { return device_user_id_; }
+
+ private:
+ const std::string token_;
+ const std::string app_id_;
+ const int64 device_user_id_;
+};
+
+CollapseKey::CollapseKey(const mcs_proto::DataMessageStanza& message)
+ : token_(message.token()),
+ app_id_(message.category()),
+ device_user_id_(message.device_user_id()) {}
+
+CollapseKey::~CollapseKey() {}
+
+bool CollapseKey::IsValid() const {
+ // Device user id is optional, but the application id and token are not.
+ return !token_.empty() && !app_id_.empty();
+}
+
+bool CollapseKey::operator<(const CollapseKey& right) const {
+ if (device_user_id_ != right.device_user_id())
+ return device_user_id_ < right.device_user_id();
+ if (app_id_ != right.app_id())
+ return app_id_ < right.app_id();
+ return token_ < right.token();
+}
+
struct ReliablePacketInfo {
ReliablePacketInfo();
~ReliablePacketInfo();
@@ -86,11 +129,38 @@ ReliablePacketInfo::ReliablePacketInfo()
}
ReliablePacketInfo::~ReliablePacketInfo() {}
-MCSClient::MCSClient(
- const base::FilePath& rmq_path,
- ConnectionFactory* connection_factory,
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
- : state_(UNINITIALIZED),
+int MCSClient::GetSendQueueSize() const {
+ return to_send_.size();
+}
+
+int MCSClient::GetResendQueueSize() const {
+ return to_resend_.size();
+}
+
+std::string MCSClient::GetStateString() const {
+ switch(state_) {
+ case UNINITIALIZED:
+ return "UNINITIALIZED";
+ case LOADED:
+ return "LOADED";
+ case CONNECTING:
+ return "CONNECTING";
+ case CONNECTED:
+ return "CONNECTED";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+MCSClient::MCSClient(const std::string& version_string,
+ base::Clock* clock,
+ ConnectionFactory* connection_factory,
+ GCMStore* gcm_store,
+ GCMStatsRecorder* recorder)
+ : version_string_(version_string),
+ clock_(clock),
+ state_(UNINITIALIZED),
android_id_(0),
security_token_(0),
connection_factory_(connection_factory),
@@ -99,11 +169,8 @@ MCSClient::MCSClient(
last_server_to_device_stream_id_received_(0),
stream_id_out_(0),
stream_id_in_(0),
- rmq_store_(rmq_path, blocking_task_runner),
- heartbeat_interval_(
- base::TimeDelta::FromSeconds(kHeartbeatDefaultSeconds)),
- heartbeat_timer_(true, true),
- blocking_task_runner_(blocking_task_runner),
+ gcm_store_(gcm_store),
+ recorder_(recorder),
weak_ptr_factory_(this) {
}
@@ -111,18 +178,17 @@ MCSClient::~MCSClient() {
}
void MCSClient::Initialize(
- const InitializationCompleteCallback& initialization_callback,
+ const ErrorCallback& error_callback,
const OnMessageReceivedCallback& message_received_callback,
- const OnMessageSentCallback& message_sent_callback) {
+ const OnMessageSentCallback& message_sent_callback,
+ scoped_ptr<GCMStore::LoadResult> load_result) {
DCHECK_EQ(state_, UNINITIALIZED);
- initialization_callback_ = initialization_callback;
+
+ state_ = LOADED;
+ mcs_error_callback_ = error_callback;
message_received_callback_ = message_received_callback;
message_sent_callback_ = message_sent_callback;
- state_ = LOADING;
- rmq_store_.Load(base::Bind(&MCSClient::OnRMQLoadFinished,
- weak_ptr_factory_.GetWeakPtr()));
-
connection_factory_->Initialize(
base::Bind(&MCSClient::ResetStateAndBuildLoginRequest,
weak_ptr_factory_.GetWeakPtr()),
@@ -131,71 +197,171 @@ void MCSClient::Initialize(
base::Bind(&MCSClient::MaybeSendMessage,
weak_ptr_factory_.GetWeakPtr()));
connection_handler_ = connection_factory_->GetConnectionHandler();
+
+ stream_id_out_ = 1; // Login request is hardcoded to id 1.
+
+ android_id_ = load_result->device_android_id;
+ security_token_ = load_result->device_security_token;
+
+ if (android_id_ == 0) {
+ DVLOG(1) << "No device credentials found, assuming new client.";
+ // No need to try and load RMQ data in that case.
+ return;
+ }
+
+ // |android_id_| is non-zero, so should |security_token_|.
+ DCHECK_NE(0u, security_token_) << "Security token invalid, while android id"
+ << " is non-zero.";
+
+ DVLOG(1) << "RMQ Load finished with " << load_result->incoming_messages.size()
+ << " incoming acks pending and "
+ << load_result->outgoing_messages.size()
+ << " outgoing messages pending.";
+
+ restored_unackeds_server_ids_ = load_result->incoming_messages;
+
+ // First go through and order the outgoing messages by recency.
+ std::map<uint64, google::protobuf::MessageLite*> ordered_messages;
+ std::vector<PersistentId> expired_ttl_ids;
+ for (GCMStore::OutgoingMessageMap::iterator iter =
+ load_result->outgoing_messages.begin();
+ iter != load_result->outgoing_messages.end(); ++iter) {
+ uint64 timestamp = 0;
+ if (!base::StringToUint64(iter->first, &timestamp)) {
+ LOG(ERROR) << "Invalid restored message.";
+ // TODO(fgorski): Error: data unreadable
+ mcs_error_callback_.Run();
+ return;
+ }
+
+ // Check if the TTL has expired for this message.
+ if (HasTTLExpired(*iter->second, clock_)) {
+ expired_ttl_ids.push_back(iter->first);
+ NotifyMessageSendStatus(*iter->second, TTL_EXCEEDED);
+ continue;
+ }
+
+ ordered_messages[timestamp] = iter->second.release();
+ }
+
+ if (!expired_ttl_ids.empty()) {
+ gcm_store_->RemoveOutgoingMessages(
+ expired_ttl_ids,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ // Now go through and add the outgoing messages to the send queue in their
+ // appropriate order (oldest at front, most recent at back).
+ for (std::map<uint64, google::protobuf::MessageLite*>::iterator
+ iter = ordered_messages.begin();
+ iter != ordered_messages.end(); ++iter) {
+ ReliablePacketInfo* packet_info = new ReliablePacketInfo();
+ packet_info->protobuf.reset(iter->second);
+ packet_info->tag = GetMCSProtoTag(*iter->second);
+ packet_info->persistent_id = base::Uint64ToString(iter->first);
+ to_send_.push_back(make_linked_ptr(packet_info));
+
+ if (packet_info->tag == kDataMessageStanzaTag) {
+ mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(
+ packet_info->protobuf.get());
+ CollapseKey collapse_key(*data_message);
+ if (collapse_key.IsValid())
+ collapse_key_map_[collapse_key] = packet_info;
+ }
+ }
}
void MCSClient::Login(uint64 android_id, uint64 security_token) {
DCHECK_EQ(state_, LOADED);
+ DCHECK(android_id_ == 0 || android_id_ == android_id);
+ DCHECK(security_token_ == 0 || security_token_ == security_token);
+
if (android_id != android_id_ && security_token != security_token_) {
DCHECK(android_id);
DCHECK(security_token);
- DCHECK(restored_unackeds_server_ids_.empty());
android_id_ = android_id;
security_token_ = security_token;
- rmq_store_.SetDeviceCredentials(android_id_,
- security_token_,
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
}
+ DCHECK(android_id_ != 0 || restored_unackeds_server_ids_.empty());
+
state_ = CONNECTING;
connection_factory_->Connect();
}
-void MCSClient::SendMessage(const MCSMessage& message, bool use_rmq) {
- DCHECK_EQ(state_, CONNECTED);
+void MCSClient::SendMessage(const MCSMessage& message) {
+ int ttl = GetTTL(message.GetProtobuf());
+ DCHECK_GE(ttl, 0);
if (to_send_.size() > kMaxSendQueueSize) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(message_sent_callback_, "Message queue full."));
+ NotifyMessageSendStatus(message.GetProtobuf(), QUEUE_SIZE_LIMIT_REACHED);
return;
}
if (message.size() > kMaxMessageBytes) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(message_sent_callback_, "Message too large."));
+ NotifyMessageSendStatus(message.GetProtobuf(), MESSAGE_TOO_LARGE);
return;
}
- ReliablePacketInfo* packet_info = new ReliablePacketInfo();
+ scoped_ptr<ReliablePacketInfo> packet_info(new ReliablePacketInfo());
+ packet_info->tag = message.tag();
packet_info->protobuf = message.CloneProtobuf();
- if (use_rmq) {
- PersistentId persistent_id = GetNextPersistentId();
- DVLOG(1) << "Setting persistent id to " << persistent_id;
- packet_info->persistent_id = persistent_id;
- SetPersistentId(persistent_id,
- packet_info->protobuf.get());
- rmq_store_.AddOutgoingMessage(persistent_id,
- MCSMessage(message.tag(),
- *(packet_info->protobuf)),
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
- } else {
- // Check that there is an active connection to the endpoint.
- if (!connection_handler_->CanSendMessage()) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(message_sent_callback_, "Unable to reach endpoint"));
+ if (ttl > 0) {
+ DCHECK_EQ(message.tag(), kDataMessageStanzaTag);
+
+ // First check if this message should replace a pending message with the
+ // same collapse key.
+ mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(
+ packet_info->protobuf.get());
+ CollapseKey collapse_key(*data_message);
+ if (collapse_key.IsValid() && collapse_key_map_.count(collapse_key) > 0) {
+ ReliablePacketInfo* original_packet = collapse_key_map_[collapse_key];
+ DVLOG(1) << "Found matching collapse key, Reusing persistent id of "
+ << original_packet->persistent_id;
+ original_packet->protobuf = packet_info->protobuf.Pass();
+ SetPersistentId(original_packet->persistent_id,
+ original_packet->protobuf.get());
+ gcm_store_->OverwriteOutgoingMessage(
+ original_packet->persistent_id,
+ message,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ // The message is already queued, return.
return;
+ } else {
+ PersistentId persistent_id = GetNextPersistentId();
+ DVLOG(1) << "Setting persistent id to " << persistent_id;
+ packet_info->persistent_id = persistent_id;
+ SetPersistentId(persistent_id, packet_info->protobuf.get());
+ if (!gcm_store_->AddOutgoingMessage(
+ persistent_id,
+ MCSMessage(message.tag(), *(packet_info->protobuf)),
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()))) {
+ NotifyMessageSendStatus(message.GetProtobuf(),
+ APP_QUEUE_SIZE_LIMIT_REACHED);
+ return;
+ }
}
+
+ if (collapse_key.IsValid())
+ collapse_key_map_[collapse_key] = packet_info.get();
+ } else if (!connection_factory_->IsEndpointReachable()) {
+ DVLOG(1) << "No active connection, dropping message.";
+ NotifyMessageSendStatus(message.GetProtobuf(), NO_CONNECTION_ON_ZERO_TTL);
+ return;
}
- to_send_.push_back(make_linked_ptr(packet_info));
- MaybeSendMessage();
-}
-void MCSClient::Destroy() {
- rmq_store_.Destroy(base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
+ to_send_.push_back(make_linked_ptr(packet_info.release()));
+
+ // Notify that the messages has been succsfully queued for sending.
+ // TODO(jianli): We should report QUEUED after writing to GCM store succeeds.
+ NotifyMessageSendStatus(message.GetProtobuf(), QUEUED);
+
+ MaybeSendMessage();
}
void MCSClient::ResetStateAndBuildLoginRequest(
@@ -207,7 +373,7 @@ void MCSClient::ResetStateAndBuildLoginRequest(
last_device_to_server_stream_id_received_ = 0;
last_server_to_device_stream_id_received_ = 0;
- // TODO(zea): expire all messages older than their TTL.
+ heartbeat_manager_.Stop();
// Add any pending acknowledgments to the list of ids.
for (StreamIdToPersistentIdMap::const_iterator iter =
@@ -229,7 +395,9 @@ void MCSClient::ResetStateAndBuildLoginRequest(
acked_server_ids_.clear();
// Then build the request, consuming all pending acknowledgments.
- request->Swap(BuildLoginRequest(android_id_, security_token_).get());
+ request->Swap(BuildLoginRequest(android_id_,
+ security_token_,
+ version_string_).get());
for (PersistentIdList::const_iterator iter =
restored_unackeds_server_ids_.begin();
iter != restored_unackeds_server_ids_.end(); ++iter) {
@@ -245,74 +413,49 @@ void MCSClient::ResetStateAndBuildLoginRequest(
to_send_.push_front(to_resend_.back());
to_resend_.pop_back();
}
- DVLOG(1) << "Resetting state, with " << request->received_persistent_id_size()
- << " incoming acks pending, and " << to_send_.size()
- << " pending outgoing messages.";
-
- heartbeat_timer_.Stop();
-
- state_ = CONNECTING;
-}
-void MCSClient::SendHeartbeat() {
- SendMessage(MCSMessage(kHeartbeatPingTag, mcs_proto::HeartbeatPing()),
- false);
-}
-
-void MCSClient::OnRMQLoadFinished(const RMQStore::LoadResult& result) {
- if (!result.success) {
- state_ = UNINITIALIZED;
- LOG(ERROR) << "Failed to load/create RMQ state. Not connecting.";
- initialization_callback_.Run(false, 0, 0);
- return;
+ // Drop all TTL == 0 or expired TTL messages from the queue.
+ std::deque<MCSPacketInternal> new_to_send;
+ std::vector<PersistentId> expired_ttl_ids;
+ while (!to_send_.empty()) {
+ MCSPacketInternal packet = PopMessageForSend();
+ if (GetTTL(*packet->protobuf) > 0 &&
+ !HasTTLExpired(*packet->protobuf, clock_)) {
+ new_to_send.push_back(packet);
+ } else {
+ // If the TTL was 0 there is no persistent id, so no need to remove the
+ // message from the persistent store.
+ if (!packet->persistent_id.empty())
+ expired_ttl_ids.push_back(packet->persistent_id);
+ NotifyMessageSendStatus(*packet->protobuf, TTL_EXCEEDED);
+ }
}
- state_ = LOADED;
- stream_id_out_ = 1; // Login request is hardcoded to id 1.
- if (result.device_android_id == 0 || result.device_security_token == 0) {
- DVLOG(1) << "No device credentials found, assuming new client.";
- initialization_callback_.Run(true, 0, 0);
- return;
+ if (!expired_ttl_ids.empty()) {
+ DVLOG(1) << "Connection reset, " << expired_ttl_ids.size()
+ << " messages expired.";
+ gcm_store_->RemoveOutgoingMessages(
+ expired_ttl_ids,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
}
- android_id_ = result.device_android_id;
- security_token_ = result.device_security_token;
+ to_send_.swap(new_to_send);
- DVLOG(1) << "RMQ Load finished with " << result.incoming_messages.size()
- << " incoming acks pending and " << result.outgoing_messages.size()
- << " outgoing messages pending.";
-
- restored_unackeds_server_ids_ = result.incoming_messages;
-
- // First go through and order the outgoing messages by recency.
- std::map<uint64, google::protobuf::MessageLite*> ordered_messages;
- for (std::map<PersistentId, google::protobuf::MessageLite*>::const_iterator
- iter = result.outgoing_messages.begin();
- iter != result.outgoing_messages.end(); ++iter) {
- uint64 timestamp = 0;
- if (!base::StringToUint64(iter->first, &timestamp)) {
- LOG(ERROR) << "Invalid restored message.";
- return;
- }
- ordered_messages[timestamp] = iter->second;
- }
+ DVLOG(1) << "Resetting state, with " << request->received_persistent_id_size()
+ << " incoming acks pending, and " << to_send_.size()
+ << " pending outgoing messages.";
- // Now go through and add the outgoing messages to the send queue in their
- // appropriate order (oldest at front, most recent at back).
- for (std::map<uint64, google::protobuf::MessageLite*>::const_iterator
- iter = ordered_messages.begin();
- iter != ordered_messages.end(); ++iter) {
- ReliablePacketInfo* packet_info = new ReliablePacketInfo();
- packet_info->protobuf.reset(iter->second);
- packet_info->persistent_id = base::Uint64ToString(iter->first);
- to_send_.push_back(make_linked_ptr(packet_info));
- }
+ state_ = CONNECTING;
+}
- initialization_callback_.Run(true, android_id_, security_token_);
+void MCSClient::SendHeartbeat() {
+ SendMessage(MCSMessage(kHeartbeatPingTag, mcs_proto::HeartbeatPing()));
}
-void MCSClient::OnRMQUpdateFinished(bool success) {
- LOG_IF(ERROR, !success) << "RMQ Update failed!";
+void MCSClient::OnGCMUpdateFinished(bool success) {
+ LOG_IF(ERROR, !success) << "GCM Update failed!";
+ UMA_HISTOGRAM_BOOLEAN("GCM.StoreUpdateSucceeded", success);
// TODO(zea): Rebuild the store from scratch in case of persistence failure?
}
@@ -320,25 +463,56 @@ void MCSClient::MaybeSendMessage() {
if (to_send_.empty())
return;
- if (!connection_handler_->CanSendMessage())
+ // If the connection has been reset, do nothing. On reconnection
+ // MaybeSendMessage will be automatically invoked again.
+ // TODO(zea): consider doing TTL expiration at connection reset time, rather
+ // than reconnect time.
+ if (!connection_factory_->IsEndpointReachable())
return;
- // TODO(zea): drop messages older than their TTL.
-
+ MCSPacketInternal packet = PopMessageForSend();
+ if (HasTTLExpired(*packet->protobuf, clock_)) {
+ DCHECK(!packet->persistent_id.empty());
+ DVLOG(1) << "Dropping expired message " << packet->persistent_id << ".";
+ NotifyMessageSendStatus(*packet->protobuf, TTL_EXCEEDED);
+ gcm_store_->RemoveOutgoingMessage(
+ packet->persistent_id,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MCSClient::MaybeSendMessage,
+ weak_ptr_factory_.GetWeakPtr()));
+ return;
+ }
DVLOG(1) << "Pending output message found, sending.";
- MCSPacketInternal packet = to_send_.front();
- to_send_.pop_front();
if (!packet->persistent_id.empty())
to_resend_.push_back(packet);
SendPacketToWire(packet.get());
}
void MCSClient::SendPacketToWire(ReliablePacketInfo* packet_info) {
- // Reset the heartbeat interval.
- heartbeat_timer_.Reset();
packet_info->stream_id = ++stream_id_out_;
DVLOG(1) << "Sending packet of type " << packet_info->protobuf->GetTypeName();
+ // Set the queued time as necessary.
+ if (packet_info->tag == kDataMessageStanzaTag) {
+ mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(
+ packet_info->protobuf.get());
+ uint64 sent = data_message->sent();
+ DCHECK_GT(sent, 0U);
+ int queued = (clock_->Now().ToInternalValue() /
+ base::Time::kMicrosecondsPerSecond) - sent;
+ DVLOG(1) << "Message was queued for " << queued << " seconds.";
+ data_message->set_queued(queued);
+ recorder_->RecordDataSentToWire(
+ data_message->category(),
+ data_message->to(),
+ data_message->id(),
+ queued);
+ }
+
// Set the proper last received stream id to acknowledge received server
// packets.
DVLOG(1) << "Setting last stream id received to "
@@ -373,6 +547,9 @@ void MCSClient::HandleMCSDataMesssage(
scoped_ptr<mcs_proto::DataMessageStanza> response(
new mcs_proto::DataMessageStanza());
response->set_from(kGCMFromField);
+ response->set_sent(clock_->Now().ToInternalValue() /
+ base::Time::kMicrosecondsPerSecond);
+ response->set_ttl(0);
bool send = false;
for (int i = 0; i < data_message->app_data_size(); ++i) {
const mcs_proto::AppData& app_data = data_message->app_data(i);
@@ -390,8 +567,7 @@ void MCSClient::HandleMCSDataMesssage(
if (send) {
SendMessage(
MCSMessage(kDataMessageStanzaTag,
- response.PassAs<const google::protobuf::MessageLite>()),
- false);
+ response.PassAs<const google::protobuf::MessageLite>()));
}
}
@@ -430,9 +606,9 @@ void MCSClient::HandlePacketFromWire(
++stream_id_in_;
if (!persistent_id.empty()) {
unacked_server_ids_[stream_id_in_] = persistent_id;
- rmq_store_.AddIncomingMessage(persistent_id,
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
+ gcm_store_->AddIncomingMessage(persistent_id,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
}
DVLOG(1) << "Received message of type " << protobuf->GetTypeName()
@@ -445,25 +621,36 @@ void MCSClient::HandlePacketFromWire(
unacked_server_ids_.size() % kUnackedMessageBeforeStreamAck == 0) {
SendMessage(MCSMessage(kIqStanzaTag,
BuildStreamAck().
- PassAs<const google::protobuf::MessageLite>()),
- false);
+ PassAs<const google::protobuf::MessageLite>()));
}
+ // The connection is alive, treat this message as a heartbeat ack.
+ heartbeat_manager_.OnHeartbeatAcked();
+
switch (tag) {
case kLoginResponseTag: {
+ DCHECK_EQ(CONNECTING, state_);
mcs_proto::LoginResponse* login_response =
reinterpret_cast<mcs_proto::LoginResponse*>(protobuf.get());
DVLOG(1) << "Received login response:";
DVLOG(1) << " Id: " << login_response->id();
DVLOG(1) << " Timestamp: " << login_response->server_timestamp();
- if (login_response->has_error()) {
+ if (login_response->has_error() && login_response->error().code() != 0) {
state_ = UNINITIALIZED;
DVLOG(1) << " Error code: " << login_response->error().code();
DVLOG(1) << " Error message: " << login_response->error().message();
- initialization_callback_.Run(false, 0, 0);
+ LOG(ERROR) << "Failed to log in to GCM, resetting connection.";
+ connection_factory_->SignalConnectionReset(
+ ConnectionFactory::LOGIN_FAILURE);
+ mcs_error_callback_.Run();
return;
}
+ if (login_response->has_heartbeat_config()) {
+ heartbeat_manager_.UpdateHeartbeatConfig(
+ login_response->heartbeat_config());
+ }
+
state_ = CONNECTED;
stream_id_in_ = 1; // To account for the login response.
DCHECK_EQ(1U, stream_id_out_);
@@ -484,29 +671,29 @@ void MCSClient::HandlePacketFromWire(
weak_ptr_factory_.GetWeakPtr()));
}
- heartbeat_timer_.Start(FROM_HERE,
- heartbeat_interval_,
- base::Bind(&MCSClient::SendHeartbeat,
- weak_ptr_factory_.GetWeakPtr()));
+ heartbeat_manager_.Start(
+ base::Bind(&MCSClient::SendHeartbeat,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&MCSClient::OnConnectionResetByHeartbeat,
+ weak_ptr_factory_.GetWeakPtr()));
return;
}
case kHeartbeatPingTag:
DCHECK_GE(stream_id_in_, 1U);
DVLOG(1) << "Received heartbeat ping, sending ack.";
SendMessage(
- MCSMessage(kHeartbeatAckTag, mcs_proto::HeartbeatAck()), false);
+ MCSMessage(kHeartbeatAckTag, mcs_proto::HeartbeatAck()));
return;
case kHeartbeatAckTag:
DCHECK_GE(stream_id_in_, 1U);
DVLOG(1) << "Received heartbeat ack.";
- // TODO(zea): add logic to reconnect if no ack received within a certain
- // timeout (with backoff).
+ // Do nothing else, all messages act as heartbeat acks.
return;
case kCloseTag:
- LOG(ERROR) << "Received close command, closing connection.";
- state_ = UNINITIALIZED;
- initialization_callback_.Run(false, 0, 0);
- // TODO(zea): should this happen in non-error cases? Reconnect?
+ LOG(ERROR) << "Received close command, resetting connection.";
+ state_ = LOADED;
+ connection_factory_->SignalConnectionReset(
+ ConnectionFactory::CLOSE_COMMAND);
return;
case kIqStanzaTag: {
DCHECK_GE(stream_id_in_, 1U);
@@ -565,58 +752,85 @@ void MCSClient::HandleStreamAck(StreamId last_stream_id_received) {
const MCSPacketInternal& outgoing_packet = to_resend_.front();
acked_outgoing_persistent_ids.push_back(outgoing_packet->persistent_id);
acked_outgoing_stream_ids.push_back(outgoing_packet->stream_id);
+ NotifyMessageSendStatus(*outgoing_packet->protobuf, SENT);
to_resend_.pop_front();
}
DVLOG(1) << "Server acked " << acked_outgoing_persistent_ids.size()
<< " outgoing messages, " << to_resend_.size()
<< " remaining unacked";
- rmq_store_.RemoveOutgoingMessages(acked_outgoing_persistent_ids,
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
+ gcm_store_->RemoveOutgoingMessages(
+ acked_outgoing_persistent_ids,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
HandleServerConfirmedReceipt(last_stream_id_received);
}
void MCSClient::HandleSelectiveAck(const PersistentIdList& id_list) {
- // First check the to_resend_ queue. Acknowledgments should always happen
- // in the order they were sent, so if messages are present they should match
- // the acknowledge list.
- PersistentIdList::const_iterator iter = id_list.begin();
- for (; iter != id_list.end() && !to_resend_.empty(); ++iter) {
+ std::set<PersistentId> remaining_ids(id_list.begin(), id_list.end());
+
+ StreamId last_stream_id_received = -1;
+
+ // First check the to_resend_ queue. Acknowledgments are always contiguous,
+ // so if there's a pending message that hasn't been acked, all newer messages
+ // must also be unacked.
+ while(!to_resend_.empty() && !remaining_ids.empty()) {
const MCSPacketInternal& outgoing_packet = to_resend_.front();
- DCHECK_EQ(outgoing_packet->persistent_id, *iter);
+ if (remaining_ids.count(outgoing_packet->persistent_id) == 0)
+ break; // Newer message must be unacked too.
+ remaining_ids.erase(outgoing_packet->persistent_id);
+ NotifyMessageSendStatus(*outgoing_packet->protobuf, SENT);
// No need to re-acknowledge any server messages this message already
// acknowledged.
StreamId device_stream_id = outgoing_packet->stream_id;
- HandleServerConfirmedReceipt(device_stream_id);
-
+ if (device_stream_id > last_stream_id_received)
+ last_stream_id_received = device_stream_id;
to_resend_.pop_front();
}
// If the acknowledged ids aren't all there, they might be in the to_send_
- // queue (typically when a StreamAck confirms messages as part of a login
+ // queue (typically when a SelectiveAck confirms messages as part of a login
// response).
- for (; iter != id_list.end() && !to_send_.empty(); ++iter) {
+ while (!to_send_.empty() && !remaining_ids.empty()) {
const MCSPacketInternal& outgoing_packet = to_send_.front();
- DCHECK_EQ(outgoing_packet->persistent_id, *iter);
+ if (remaining_ids.count(outgoing_packet->persistent_id) == 0)
+ break; // Newer messages must be unacked too.
+ remaining_ids.erase(outgoing_packet->persistent_id);
+ NotifyMessageSendStatus(*outgoing_packet->protobuf, SENT);
// No need to re-acknowledge any server messages this message already
// acknowledged.
StreamId device_stream_id = outgoing_packet->stream_id;
- HandleServerConfirmedReceipt(device_stream_id);
-
- to_send_.pop_front();
+ if (device_stream_id > last_stream_id_received)
+ last_stream_id_received = device_stream_id;
+ PopMessageForSend();
}
- DCHECK(iter == id_list.end());
+ // Only handle the largest stream id value. All other stream ids are
+ // implicitly handled.
+ if (last_stream_id_received > 0)
+ HandleServerConfirmedReceipt(last_stream_id_received);
+
+ // At this point, all remaining acked ids are redundant.
+ PersistentIdList acked_ids;
+ if (remaining_ids.size() > 0) {
+ for (size_t i = 0; i < id_list.size(); ++i) {
+ if (remaining_ids.count(id_list[i]) > 0)
+ continue;
+ acked_ids.push_back(id_list[i]);
+ }
+ } else {
+ acked_ids = id_list;
+ }
- DVLOG(1) << "Server acked " << id_list.size()
+ DVLOG(1) << "Server acked " << acked_ids.size()
<< " messages, " << to_resend_.size() << " remaining unacked.";
- rmq_store_.RemoveOutgoingMessages(id_list,
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
+ gcm_store_->RemoveOutgoingMessages(
+ acked_ids,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
// Resend any remaining outgoing messages, as they were not received by the
// server.
@@ -628,12 +842,6 @@ void MCSClient::HandleSelectiveAck(const PersistentIdList& id_list) {
}
void MCSClient::HandleServerConfirmedReceipt(StreamId device_stream_id) {
- // TODO(zea): use a message id the sender understands.
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(message_sent_callback_,
- "Message " + base::UintToString(device_stream_id) + " sent."));
-
PersistentIdList acked_incoming_ids;
for (std::map<StreamId, PersistentIdList>::iterator iter =
acked_server_ids_.begin();
@@ -647,13 +855,56 @@ void MCSClient::HandleServerConfirmedReceipt(StreamId device_stream_id) {
DVLOG(1) << "Server confirmed receipt of " << acked_incoming_ids.size()
<< " acknowledged server messages.";
- rmq_store_.RemoveIncomingMessages(acked_incoming_ids,
- base::Bind(&MCSClient::OnRMQUpdateFinished,
- weak_ptr_factory_.GetWeakPtr()));
+ gcm_store_->RemoveIncomingMessages(
+ acked_incoming_ids,
+ base::Bind(&MCSClient::OnGCMUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr()));
}
MCSClient::PersistentId MCSClient::GetNextPersistentId() {
return base::Uint64ToString(base::TimeTicks::Now().ToInternalValue());
}
+void MCSClient::OnConnectionResetByHeartbeat() {
+ connection_factory_->SignalConnectionReset(
+ ConnectionFactory::HEARTBEAT_FAILURE);
+}
+
+void MCSClient::NotifyMessageSendStatus(
+ const google::protobuf::MessageLite& protobuf,
+ MessageSendStatus status) {
+ if (GetMCSProtoTag(protobuf) != kDataMessageStanzaTag)
+ return;
+
+ const mcs_proto::DataMessageStanza* data_message_stanza =
+ reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf);
+ recorder_->RecordNotifySendStatus(
+ data_message_stanza->category(),
+ data_message_stanza->to(),
+ data_message_stanza->id(),
+ status,
+ protobuf.ByteSize(),
+ data_message_stanza->ttl());
+ message_sent_callback_.Run(
+ data_message_stanza->device_user_id(),
+ data_message_stanza->category(),
+ data_message_stanza->id(),
+ status);
+}
+
+MCSClient::MCSPacketInternal MCSClient::PopMessageForSend() {
+ MCSPacketInternal packet = to_send_.front();
+ to_send_.pop_front();
+
+ if (packet->tag == kDataMessageStanzaTag) {
+ mcs_proto::DataMessageStanza* data_message =
+ reinterpret_cast<mcs_proto::DataMessageStanza*>(packet->protobuf.get());
+ CollapseKey collapse_key(*data_message);
+ if (collapse_key.IsValid())
+ collapse_key_map_.erase(collapse_key);
+ }
+
+ return packet;
+}
+
} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/mcs_client.h b/chromium/google_apis/gcm/engine/mcs_client.h
index 4de62cb127e..cc915e49087 100644
--- a/chromium/google_apis/gcm/engine/mcs_client.h
+++ b/chromium/google_apis/gcm/engine/mcs_client.h
@@ -13,11 +13,15 @@
#include "base/files/file_path.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/weak_ptr.h"
-#include "base/timer/timer.h"
#include "google_apis/gcm/base/gcm_export.h"
#include "google_apis/gcm/base/mcs_message.h"
#include "google_apis/gcm/engine/connection_handler.h"
-#include "google_apis/gcm/engine/rmq_store.h"
+#include "google_apis/gcm/engine/gcm_store.h"
+#include "google_apis/gcm/engine/heartbeat_manager.h"
+
+namespace base {
+class Clock;
+} // namespace base
namespace google {
namespace protobuf {
@@ -31,7 +35,9 @@ class LoginRequest;
namespace gcm {
+class CollapseKey;
class ConnectionFactory;
+class GCMStatsRecorder;
struct ReliablePacketInfo;
// An MCS client. This client is in charge of all communications with an
@@ -40,48 +46,74 @@ struct ReliablePacketInfo;
// network requests are performed on.
class GCM_EXPORT MCSClient {
public:
+ // Any change made to this enum should have corresponding change in the
+ // GetStateString(...) function.
enum State {
- UNINITIALIZED, // Uninitialized.
- LOADING, // Waiting for RMQ load to finish.
- LOADED, // RMQ Load finished, waiting to connect.
- CONNECTING, // Connection in progress.
- CONNECTED, // Connected and running.
+ UNINITIALIZED, // Uninitialized.
+ LOADED, // GCM Load finished, waiting to connect.
+ CONNECTING, // Connection in progress.
+ CONNECTED, // Connected and running.
};
- // Callback for informing MCSClient status. It is valid for this to be
- // invoked more than once if a permanent error is encountered after a
- // successful login was initiated.
- typedef base::Callback<
- void(bool success,
- uint64 restored_android_id,
- uint64 restored_security_token)> InitializationCompleteCallback;
+ // Any change made to this enum should have corresponding change in the
+ // GetMessageSendStatusString(...) function in mcs_client.cc.
+ enum MessageSendStatus {
+ // Message was queued succcessfully.
+ QUEUED,
+ // Message was sent to the server and the ACK was received.
+ SENT,
+ // Message not saved, because total queue size limit reached.
+ QUEUE_SIZE_LIMIT_REACHED,
+ // Message not saved, because app queue size limit reached.
+ APP_QUEUE_SIZE_LIMIT_REACHED,
+ // Message too large to send.
+ MESSAGE_TOO_LARGE,
+ // Message not send becuase of TTL = 0 and no working connection.
+ NO_CONNECTION_ON_ZERO_TTL,
+ // Message exceeded TTL.
+ TTL_EXCEEDED,
+
+ // NOTE: always keep this entry at the end. Add new status types only
+ // immediately above this line. Make sure to update the corresponding
+ // histogram enum accordingly.
+ SEND_STATUS_COUNT
+ };
+
+ // Callback for MCSClient's error conditions.
+ // TODO(fgorski): Keeping it as a callback with intention to add meaningful
+ // error information.
+ typedef base::Callback<void()> ErrorCallback;
// Callback when a message is received.
typedef base::Callback<void(const MCSMessage& message)>
OnMessageReceivedCallback;
// Callback when a message is sent (and receipt has been acknowledged by
// the MCS endpoint).
- // TODO(zea): pass some sort of structure containing more details about
- // send failures.
- typedef base::Callback<void(const std::string& message_id)>
- OnMessageSentCallback;
+ typedef base::Callback<
+ void(int64 user_serial_number,
+ const std::string& app_id,
+ const std::string& message_id,
+ MessageSendStatus status)> OnMessageSentCallback;
- MCSClient(const base::FilePath& rmq_path,
+ MCSClient(const std::string& version_string,
+ base::Clock* clock,
ConnectionFactory* connection_factory,
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
+ GCMStore* gcm_store,
+ GCMStatsRecorder* recorder);
virtual ~MCSClient();
// Initialize the client. Will load any previous id/token information as well
- // as unacknowledged message information from the RMQ storage, if it exists,
+ // as unacknowledged message information from the GCM storage, if it exists,
// passing the id/token information back via |initialization_callback| along
- // with a |success == true| result. If no RMQ information is present (and
- // this is therefore a fresh client), a clean RMQ store will be created and
+ // with a |success == true| result. If no GCM information is present (and
+ // this is therefore a fresh client), a clean GCM store will be created and
// values of 0 will be returned via |initialization_callback| with
// |success == true|.
- /// If an error loading the RMQ store is encountered,
+ /// If an error loading the GCM store is encountered,
// |initialization_callback| will be invoked with |success == false|.
- void Initialize(const InitializationCompleteCallback& initialization_callback,
+ void Initialize(const ErrorCallback& initialization_callback,
const OnMessageReceivedCallback& message_received_callback,
- const OnMessageSentCallback& message_sent_callback);
+ const OnMessageSentCallback& message_sent_callback,
+ scoped_ptr<GCMStore::LoadResult> load_result);
// Logs the client into the server. Client must be initialized.
// |android_id| and |security_token| are optional if this is not a new
@@ -90,21 +122,29 @@ class GCM_EXPORT MCSClient {
// with a valid LoginResponse.
// Login failure (typically invalid id/token) will shut down the client, and
// |initialization_callback| to be invoked with |success = false|.
- void Login(uint64 android_id, uint64 security_token);
+ virtual void Login(uint64 android_id, uint64 security_token);
// Sends a message, with or without reliable message queueing (RMQ) support.
// Will asynchronously invoke the OnMessageSent callback regardless.
- // TODO(zea): support TTL.
- void SendMessage(const MCSMessage& message, bool use_rmq);
-
- // Disconnects the client and permanently destroys the persistent RMQ store.
- // WARNING: This is permanent, and the client must be recreated with new
- // credentials afterwards.
- void Destroy();
+ // Whether to use RMQ depends on whether the protobuf has |ttl| set or not.
+ // |ttl == 0| denotes the message should only be sent if the connection is
+ // open. |ttl > 0| will keep the message saved for |ttl| seconds, after which
+ // it will be dropped if it was unable to be sent. When a message is dropped,
+ // |message_sent_callback_| is invoked with a TTL expiration error.
+ virtual void SendMessage(const MCSMessage& message);
// Returns the current state of the client.
State state() const { return state_; }
+ // Returns the size of the send message queue.
+ int GetSendQueueSize() const;
+
+ // Returns the size of the resend messaage queue.
+ int GetResendQueueSize() const;
+
+ // Returns text representation of the state enum.
+ std::string GetStateString() const;
+
private:
typedef uint32 StreamId;
typedef std::string PersistentId;
@@ -122,9 +162,8 @@ class GCM_EXPORT MCSClient {
// Send a heartbeat to the MCS server.
void SendHeartbeat();
- // RMQ Store callbacks.
- void OnRMQLoadFinished(const RMQStore::LoadResult& result);
- void OnRMQUpdateFinished(bool success);
+ // GCM Store callback.
+ void OnGCMUpdateFinished(bool success);
// Attempt to send a message.
void MaybeSendMessage();
@@ -155,11 +194,28 @@ class GCM_EXPORT MCSClient {
// Virtual for testing.
virtual PersistentId GetNextPersistentId();
+ // Helper for the heartbeat manager to signal a connection reset.
+ void OnConnectionResetByHeartbeat();
+
+ // Runs the message_sent_callback_ with send |status| of the |protobuf|.
+ void NotifyMessageSendStatus(const google::protobuf::MessageLite& protobuf,
+ MessageSendStatus status);
+
+ // Pops the next message from the front of the send queue (cleaning up
+ // any associated state).
+ MCSPacketInternal PopMessageForSend();
+
+ // Local version string. Sent on login.
+ const std::string version_string_;
+
+ // Clock for enforcing TTL. Passed in for testing.
+ base::Clock* const clock_;
+
// Client state.
State state_;
// Callbacks for owner.
- InitializationCompleteCallback initialization_callback_;
+ ErrorCallback mcs_error_callback_;
OnMessageReceivedCallback message_received_callback_;
OnMessageSentCallback message_sent_callback_;
@@ -182,6 +238,9 @@ class GCM_EXPORT MCSClient {
std::deque<MCSPacketInternal> to_send_;
std::deque<MCSPacketInternal> to_resend_;
+ // Map of collapse keys to their pending messages.
+ std::map<CollapseKey, ReliablePacketInfo*> collapse_key_map_;
+
// Last device_to_server stream id acknowledged by the server.
StreamId last_device_to_server_stream_id_received_;
// Last server_to_device stream id acknowledged by this device.
@@ -209,17 +268,14 @@ class GCM_EXPORT MCSClient {
// acknowledged on the next login attempt.
PersistentIdList restored_unackeds_server_ids_;
- // The reliable message queue persistent store.
- RMQStore rmq_store_;
+ // The GCM persistent store. Not owned.
+ GCMStore* gcm_store_;
- // ----- Heartbeats -----
- // The current heartbeat interval.
- base::TimeDelta heartbeat_interval_;
- // Timer for triggering heartbeats.
- base::Timer heartbeat_timer_;
+ // Manager to handle triggering/detecting heartbeats.
+ HeartbeatManager heartbeat_manager_;
- // The task runner for blocking tasks (i.e. persisting RMQ state to disk).
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ // Recorder that records GCM activities for debugging purpose. Not owned.
+ GCMStatsRecorder* recorder_;
base::WeakPtrFactory<MCSClient> weak_ptr_factory_;
diff --git a/chromium/google_apis/gcm/engine/mcs_client_unittest.cc b/chromium/google_apis/gcm/engine/mcs_client_unittest.cc
index 6ef140586e9..915b25d18db 100644
--- a/chromium/google_apis/gcm/engine/mcs_client_unittest.cc
+++ b/chromium/google_apis/gcm/engine/mcs_client_unittest.cc
@@ -4,14 +4,18 @@
#include "google_apis/gcm/engine/mcs_client.h"
+#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
-#include "components/webdata/encryptor/encryptor.h"
+#include "base/test/simple_test_clock.h"
+#include "google_apis/gcm/base/fake_encryptor.h"
#include "google_apis/gcm/base/mcs_util.h"
#include "google_apis/gcm/engine/fake_connection_factory.h"
#include "google_apis/gcm/engine/fake_connection_handler.h"
+#include "google_apis/gcm/engine/gcm_store_impl.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gcm {
@@ -30,27 +34,43 @@ const int kMessageBatchSize = 6;
// TODO(zea): get this (and other constants) directly from the mcs client.
const int kAckLimitSize = 10;
+// TTL value for reliable messages.
+const int kTTLValue = 5 * 60; // 5 minutes.
+
// Helper for building arbitrary data messages.
MCSMessage BuildDataMessage(const std::string& from,
const std::string& category,
+ const std::string& message_id,
int last_stream_id_received,
- const std::string persistent_id) {
+ const std::string& persistent_id,
+ int ttl,
+ uint64 sent,
+ int queued,
+ const std::string& token,
+ const uint64& user_id) {
mcs_proto::DataMessageStanza data_message;
+ data_message.set_id(message_id);
data_message.set_from(from);
data_message.set_category(category);
data_message.set_last_stream_id_received(last_stream_id_received);
if (!persistent_id.empty())
data_message.set_persistent_id(persistent_id);
+ data_message.set_ttl(ttl);
+ data_message.set_sent(sent);
+ data_message.set_queued(queued);
+ data_message.set_token(token);
+ data_message.set_device_user_id(user_id);
return MCSMessage(kDataMessageStanzaTag, data_message);
}
// MCSClient with overriden exposed persistent id logic.
class TestMCSClient : public MCSClient {
public:
- TestMCSClient(const base::FilePath& rmq_path,
+ TestMCSClient(base::Clock* clock,
ConnectionFactory* connection_factory,
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
- : MCSClient(rmq_path, connection_factory, blocking_task_runner),
+ GCMStore* gcm_store,
+ gcm::GCMStatsRecorder* recorder)
+ : MCSClient("", clock, connection_factory, gcm_store, recorder),
next_id_(0) {
}
@@ -67,10 +87,14 @@ class MCSClientTest : public testing::Test {
MCSClientTest();
virtual ~MCSClientTest();
+ virtual void SetUp() OVERRIDE;
+
void BuildMCSClient();
void InitializeClient();
+ void StoreCredentials();
void LoginClient(const std::vector<std::string>& acknowledged_ids);
+ base::SimpleTestClock* clock() { return &clock_; }
TestMCSClient* mcs_client() const { return mcs_client_.get(); }
FakeConnectionFactory* connection_factory() {
return &connection_factory_;
@@ -80,6 +104,11 @@ class MCSClientTest : public testing::Test {
uint64 restored_security_token() const { return restored_security_token_; }
MCSMessage* received_message() const { return received_message_.get(); }
std::string sent_message_id() const { return sent_message_id_;}
+ MCSClient::MessageSendStatus message_send_status() const {
+ return message_send_status_;
+ }
+
+ void SetDeviceCredentialsCallback(bool success);
FakeConnectionHandler* GetFakeHandler() const;
@@ -87,15 +116,19 @@ class MCSClientTest : public testing::Test {
void PumpLoop();
private:
- void InitializationCallback(bool success,
- uint64 restored_android_id,
- uint64 restored_security_token);
+ void ErrorCallback();
void MessageReceivedCallback(const MCSMessage& message);
- void MessageSentCallback(const std::string& message_id);
+ void MessageSentCallback(int64 user_serial_number,
+ const std::string& app_id,
+ const std::string& message_id,
+ MCSClient::MessageSendStatus status);
+
+ base::SimpleTestClock clock_;
base::ScopedTempDir temp_directory_;
base::MessageLoop message_loop_;
scoped_ptr<base::RunLoop> run_loop_;
+ scoped_ptr<GCMStore> gcm_store_;
FakeConnectionFactory connection_factory_;
scoped_ptr<TestMCSClient> mcs_client_;
@@ -104,46 +137,58 @@ class MCSClientTest : public testing::Test {
uint64 restored_security_token_;
scoped_ptr<MCSMessage> received_message_;
std::string sent_message_id_;
+ MCSClient::MessageSendStatus message_send_status_;
+
+ gcm::FakeGCMStatsRecorder recorder_;
};
MCSClientTest::MCSClientTest()
: run_loop_(new base::RunLoop()),
- init_success_(false),
+ init_success_(true),
restored_android_id_(0),
- restored_security_token_(0) {
+ restored_security_token_(0),
+ message_send_status_(MCSClient::SENT) {
EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
run_loop_.reset(new base::RunLoop());
- // On OSX, prevent the Keychain permissions popup during unit tests.
-#if defined(OS_MACOSX)
- Encryptor::UseMockKeychain(true);
-#endif
+ // Advance the clock to a non-zero time.
+ clock_.Advance(base::TimeDelta::FromSeconds(1));
}
MCSClientTest::~MCSClientTest() {}
+void MCSClientTest::SetUp() {
+ testing::Test::SetUp();
+}
+
void MCSClientTest::BuildMCSClient() {
- mcs_client_.reset(
- new TestMCSClient(temp_directory_.path(),
- &connection_factory_,
- message_loop_.message_loop_proxy()));
+ gcm_store_.reset(new GCMStoreImpl(
+ temp_directory_.path(),
+ message_loop_.message_loop_proxy(),
+ make_scoped_ptr<Encryptor>(new FakeEncryptor)));
+ mcs_client_.reset(new TestMCSClient(&clock_,
+ &connection_factory_,
+ gcm_store_.get(),
+ &recorder_));
}
void MCSClientTest::InitializeClient() {
- mcs_client_->Initialize(base::Bind(&MCSClientTest::InitializationCallback,
- base::Unretained(this)),
- base::Bind(&MCSClientTest::MessageReceivedCallback,
- base::Unretained(this)),
- base::Bind(&MCSClientTest::MessageSentCallback,
- base::Unretained(this)));
- run_loop_->Run();
+ gcm_store_->Load(base::Bind(
+ &MCSClient::Initialize,
+ base::Unretained(mcs_client_.get()),
+ base::Bind(&MCSClientTest::ErrorCallback,
+ base::Unretained(this)),
+ base::Bind(&MCSClientTest::MessageReceivedCallback,
+ base::Unretained(this)),
+ base::Bind(&MCSClientTest::MessageSentCallback, base::Unretained(this))));
+ run_loop_->RunUntilIdle();
run_loop_.reset(new base::RunLoop());
}
void MCSClientTest::LoginClient(
const std::vector<std::string>& acknowledged_ids) {
scoped_ptr<mcs_proto::LoginRequest> login_request =
- BuildLoginRequest(kAndroidId, kSecurityToken);
+ BuildLoginRequest(kAndroidId, kSecurityToken, "");
for (size_t i = 0; i < acknowledged_ids.size(); ++i)
login_request->add_received_persistent_id(acknowledged_ids[i]);
GetFakeHandler()->ExpectOutgoingMessage(
@@ -154,6 +199,15 @@ void MCSClientTest::LoginClient(
run_loop_.reset(new base::RunLoop());
}
+void MCSClientTest::StoreCredentials() {
+ gcm_store_->SetDeviceCredentials(
+ kAndroidId, kSecurityToken,
+ base::Bind(&MCSClientTest::SetDeviceCredentialsCallback,
+ base::Unretained(this)));
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop());
+}
+
FakeConnectionHandler* MCSClientTest::GetFakeHandler() const {
return reinterpret_cast<FakeConnectionHandler*>(
connection_factory_.GetConnectionHandler());
@@ -169,13 +223,9 @@ void MCSClientTest::PumpLoop() {
run_loop_.reset(new base::RunLoop());
}
-void MCSClientTest::InitializationCallback(bool success,
- uint64 restored_android_id,
- uint64 restored_security_token) {
- init_success_ = success;
- restored_android_id_ = restored_android_id;
- restored_security_token_ = restored_security_token;
- DVLOG(1) << "Initialization callback invoked, killing loop.";
+void MCSClientTest::ErrorCallback() {
+ init_success_ = false;
+ DVLOG(1) << "Error callback invoked, killing loop.";
run_loop_->Quit();
}
@@ -185,8 +235,18 @@ void MCSClientTest::MessageReceivedCallback(const MCSMessage& message) {
run_loop_->Quit();
}
-void MCSClientTest::MessageSentCallback(const std::string& message_id) {
+void MCSClientTest::MessageSentCallback(int64 user_serial_number,
+ const std::string& app_id,
+ const std::string& message_id,
+ MCSClient::MessageSendStatus status) {
DVLOG(1) << "Message sent callback invoked, killing loop.";
+ sent_message_id_ = message_id;
+ message_send_status_ = status;
+ run_loop_->Quit();
+}
+
+void MCSClientTest::SetDeviceCredentialsCallback(bool success) {
+ ASSERT_TRUE(success);
run_loop_->Quit();
}
@@ -194,8 +254,6 @@ void MCSClientTest::MessageSentCallback(const std::string& message_id) {
TEST_F(MCSClientTest, InitializeNew) {
BuildMCSClient();
InitializeClient();
- EXPECT_EQ(0U, restored_android_id());
- EXPECT_EQ(0U, restored_security_token());
EXPECT_TRUE(init_success());
}
@@ -206,11 +264,10 @@ TEST_F(MCSClientTest, InitializeExisting) {
InitializeClient();
LoginClient(std::vector<std::string>());
- // Rebuild the client, to reload from the RMQ.
+ // Rebuild the client, to reload from the GCM store.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
- EXPECT_EQ(kAndroidId, restored_android_id());
- EXPECT_EQ(kSecurityToken, restored_security_token());
EXPECT_TRUE(init_success());
}
@@ -225,15 +282,18 @@ TEST_F(MCSClientTest, LoginSuccess) {
EXPECT_EQ(kLoginResponseTag, received_message()->tag());
}
-// Encounter a server error during the login attempt.
+// Encounter a server error during the login attempt. Should trigger a
+// reconnect.
TEST_F(MCSClientTest, FailLogin) {
BuildMCSClient();
InitializeClient();
GetFakeHandler()->set_fail_login(true);
+ connection_factory()->set_delay_reconnect(true);
LoginClient(std::vector<std::string>());
EXPECT_FALSE(connection_factory()->IsEndpointReachable());
EXPECT_FALSE(init_success());
EXPECT_FALSE(received_message());
+ EXPECT_TRUE(connection_factory()->reconnect_pending());
}
// Send a message without RMQ support.
@@ -241,11 +301,29 @@ TEST_F(MCSClientTest, SendMessageNoRMQ) {
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
- MCSMessage message(BuildDataMessage("from", "category", 1, ""));
+ MCSMessage message(
+ BuildDataMessage("from", "category", "X", 1, "", 0, 1, 0, "", 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, false);
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ mcs_client()->SendMessage(message);
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+}
+
+// Send a message without RMQ support while disconnected. Message send should
+// fail immediately, invoking callback.
+TEST_F(MCSClientTest, SendMessageNoRMQWhileDisconnected) {
+ BuildMCSClient();
+ InitializeClient();
+
+ EXPECT_TRUE(sent_message_id().empty());
+ MCSMessage message(
+ BuildDataMessage("from", "category", "X", 1, "", 0, 1, 0, "", 0));
+ mcs_client()->SendMessage(message);
+
+ // Message sent callback should be invoked, but no message should actually
+ // be sent.
+ EXPECT_EQ("X", sent_message_id());
+ EXPECT_EQ(MCSClient::NO_CONNECTION_ON_ZERO_TTL, message_send_status());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Send a message with RMQ support.
@@ -253,11 +331,11 @@ TEST_F(MCSClientTest, SendMessageRMQ) {
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
- MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, 0, "", 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ mcs_client()->SendMessage(message);
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Send a message with RMQ support while disconnected. On reconnect, the message
@@ -267,26 +345,31 @@ TEST_F(MCSClientTest, SendMessageRMQWhileDisconnected) {
InitializeClient();
LoginClient(std::vector<std::string>());
GetFakeHandler()->set_fail_send(true);
- MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, 0, "", 0));
// The initial (failed) send.
GetFakeHandler()->ExpectOutgoingMessage(message);
// The login request.
GetFakeHandler()->ExpectOutgoingMessage(
- MCSMessage(kLoginRequestTag,
- BuildLoginRequest(kAndroidId, kSecurityToken).
- PassAs<const google::protobuf::MessageLite>()));
+ MCSMessage(
+ kLoginRequestTag,
+ BuildLoginRequest(kAndroidId, kSecurityToken, "").
+ PassAs<const google::protobuf::MessageLite>()));
// The second (re)send.
- GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
- EXPECT_FALSE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ MCSMessage message2(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, kTTLValue - 1, "", 0));
+ GetFakeHandler()->ExpectOutgoingMessage(message2);
+ mcs_client()->SendMessage(message);
+ PumpLoop(); // Wait for the queuing to happen.
+ EXPECT_EQ(MCSClient::QUEUED, message_send_status());
+ EXPECT_FALSE(GetFakeHandler()->AllOutgoingMessagesReceived());
GetFakeHandler()->set_fail_send(false);
+ clock()->Advance(base::TimeDelta::FromSeconds(kTTLValue - 1));
connection_factory()->Connect();
WaitForMCSEvent(); // Wait for the login to finish.
PumpLoop(); // Wait for the send to happen.
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Send a message with RMQ support without receiving an acknowledgement. On
@@ -296,23 +379,28 @@ TEST_F(MCSClientTest, SendMessageRMQOnRestart) {
InitializeClient();
LoginClient(std::vector<std::string>());
GetFakeHandler()->set_fail_send(true);
- MCSMessage message(BuildDataMessage("from", "category", 1, "1"));
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, 0, "", 0));
// The initial (failed) send.
GetFakeHandler()->ExpectOutgoingMessage(message);
GetFakeHandler()->set_fail_send(false);
- mcs_client()->SendMessage(message, true);
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ mcs_client()->SendMessage(message);
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Rebuild the client, which should resend the old message.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
+
+ clock()->Advance(base::TimeDelta::FromSeconds(kTTLValue - 1));
+ MCSMessage message2(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, kTTLValue - 1, "", 0));
LoginClient(std::vector<std::string>());
- GetFakeHandler()->ExpectOutgoingMessage(message);
+ GetFakeHandler()->ExpectOutgoingMessage(message2);
PumpLoop();
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Send messages with RMQ support, followed by receiving a stream ack. On
@@ -324,13 +412,21 @@ TEST_F(MCSClientTest, SendMessageRMQWithStreamAck) {
// Send some messages.
for (int i = 1; i <= kMessageBatchSize; ++i) {
- MCSMessage message(
- BuildDataMessage("from", "category", 1, base::IntToString(i)));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ "X",
+ 1,
+ base::IntToString(i),
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
+ mcs_client()->SendMessage(message);
+ PumpLoop();
}
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Receive the ack.
scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
@@ -341,6 +437,7 @@ TEST_F(MCSClientTest, SendMessageRMQWithStreamAck) {
WaitForMCSEvent();
// Reconnect and ensure no messages are resent.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
@@ -358,16 +455,25 @@ TEST_F(MCSClientTest, SendMessageRMQAckOnReconnect) {
std::vector<std::string> id_list;
for (int i = 1; i <= kMessageBatchSize; ++i) {
id_list.push_back(base::IntToString(i));
- MCSMessage message(
- BuildDataMessage("from", "category", 1, id_list.back()));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ id_list.back(),
+ 1,
+ id_list.back(),
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
+ mcs_client()->SendMessage(message);
+ PumpLoop();
}
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Rebuild the client, and receive an acknowledgment for the messages as
// part of the login response.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
@@ -375,9 +481,7 @@ TEST_F(MCSClientTest, SendMessageRMQAckOnReconnect) {
GetFakeHandler()->ReceiveMessage(
MCSMessage(kIqStanzaTag,
ack.PassAs<const google::protobuf::MessageLite>()));
- WaitForMCSEvent();
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Send messages with RMQ support. On restart, receive a SelectiveAck with
@@ -392,16 +496,25 @@ TEST_F(MCSClientTest, SendMessageRMQPartialAckOnReconnect) {
std::vector<std::string> id_list;
for (int i = 1; i <= kMessageBatchSize; ++i) {
id_list.push_back(base::IntToString(i));
- MCSMessage message(
- BuildDataMessage("from", "category", 1, id_list.back()));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ id_list.back(),
+ 1,
+ id_list.back(),
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
+ mcs_client()->SendMessage(message);
+ PumpLoop();
}
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Rebuild the client, and receive an acknowledgment for the messages as
// part of the login response.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
@@ -414,11 +527,16 @@ TEST_F(MCSClientTest, SendMessageRMQPartialAckOnReconnect) {
id_list.begin() + kMessageBatchSize / 2,
id_list.end());
for (int i = 1; i <= kMessageBatchSize / 2; ++i) {
- MCSMessage message(
- BuildDataMessage("from",
- "category",
- 2,
- remaining_ids[i - 1]));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ remaining_ids[i - 1],
+ 2,
+ remaining_ids[i - 1],
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
}
scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(acked_ids));
@@ -426,8 +544,8 @@ TEST_F(MCSClientTest, SendMessageRMQPartialAckOnReconnect) {
MCSMessage(kIqStanzaTag,
ack.PassAs<const google::protobuf::MessageLite>()));
WaitForMCSEvent();
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Receive some messages. On restart, the login request should contain the
@@ -441,14 +559,15 @@ TEST_F(MCSClientTest, AckOnLogin) {
std::vector<std::string> id_list;
for (int i = 1; i <= kMessageBatchSize; ++i) {
id_list.push_back(base::IntToString(i));
- MCSMessage message(
- BuildDataMessage("from", "category", i, id_list.back()));
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, id_list.back(), kTTLValue, 1, 0, "", 0));
GetFakeHandler()->ReceiveMessage(message);
WaitForMCSEvent();
PumpLoop();
}
// Restart the client.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
LoginClient(id_list);
@@ -465,20 +584,34 @@ TEST_F(MCSClientTest, AckOnSend) {
std::vector<std::string> id_list;
for (int i = 1; i <= kMessageBatchSize; ++i) {
id_list.push_back(base::IntToString(i));
- MCSMessage message(
- BuildDataMessage("from", "category", i, id_list.back()));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ id_list.back(),
+ 1,
+ id_list.back(),
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ReceiveMessage(message);
- WaitForMCSEvent();
PumpLoop();
}
// Trigger a message send, which should acknowledge via stream ack.
- MCSMessage message(
- BuildDataMessage("from", "category", kMessageBatchSize + 1, "1"));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ "X",
+ kMessageBatchSize + 1,
+ "1",
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ExpectOutgoingMessage(message);
- mcs_client()->SendMessage(message, true);
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ mcs_client()->SendMessage(message);
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Receive the ack limit in messages, which should trigger an automatic
@@ -499,14 +632,21 @@ TEST_F(MCSClientTest, AckWhenLimitReachedWithHeartbeat) {
std::vector<std::string> id_list;
for (int i = 1; i <= kAckLimitSize; ++i) {
id_list.push_back(base::IntToString(i));
- MCSMessage message(
- BuildDataMessage("from", "category", i, id_list.back()));
+ MCSMessage message(BuildDataMessage("from",
+ "category",
+ id_list.back(),
+ 1,
+ id_list.back(),
+ kTTLValue,
+ 1,
+ 0,
+ "",
+ 0));
GetFakeHandler()->ReceiveMessage(message);
WaitForMCSEvent();
PumpLoop();
}
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Receive a heartbeat confirming the ack (and receive the heartbeat ack).
scoped_ptr<mcs_proto::HeartbeatPing> heartbeat(
@@ -523,16 +663,135 @@ TEST_F(MCSClientTest, AckWhenLimitReachedWithHeartbeat) {
GetFakeHandler()->ReceiveMessage(
MCSMessage(kHeartbeatPingTag,
heartbeat.PassAs<const google::protobuf::MessageLite>()));
- WaitForMCSEvent();
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
// Rebuild the client. Nothing should be sent on login.
+ StoreCredentials();
BuildMCSClient();
InitializeClient();
LoginClient(std::vector<std::string>());
- EXPECT_TRUE(GetFakeHandler()->
- AllOutgoingMessagesReceived());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+}
+
+// If a message's TTL has expired by the time it reaches the front of the send
+// queue, it should be dropped.
+TEST_F(MCSClientTest, ExpiredTTLOnSend) {
+ BuildMCSClient();
+ InitializeClient();
+ LoginClient(std::vector<std::string>());
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, 0, "", 0));
+
+ // Advance time to after the TTL.
+ clock()->Advance(base::TimeDelta::FromSeconds(kTTLValue + 2));
+ EXPECT_TRUE(sent_message_id().empty());
+ mcs_client()->SendMessage(message);
+
+ // No messages should be sent, but the callback should still be invoked.
+ EXPECT_EQ("X", sent_message_id());
+ EXPECT_EQ(MCSClient::TTL_EXCEEDED, message_send_status());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+}
+
+TEST_F(MCSClientTest, ExpiredTTLOnRestart) {
+ BuildMCSClient();
+ InitializeClient();
+ LoginClient(std::vector<std::string>());
+ GetFakeHandler()->set_fail_send(true);
+ MCSMessage message(BuildDataMessage(
+ "from", "category", "X", 1, "1", kTTLValue, 1, 0, "", 0));
+
+ // The initial (failed) send.
+ GetFakeHandler()->ExpectOutgoingMessage(message);
+ GetFakeHandler()->set_fail_send(false);
+ mcs_client()->SendMessage(message);
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+
+ // Move the clock forward and rebuild the client, which should fail the
+ // message send on restart.
+ clock()->Advance(base::TimeDelta::FromSeconds(kTTLValue + 2));
+ StoreCredentials();
+ BuildMCSClient();
+ InitializeClient();
+ LoginClient(std::vector<std::string>());
+ PumpLoop();
+ EXPECT_EQ("X", sent_message_id());
+ EXPECT_EQ(MCSClient::TTL_EXCEEDED, message_send_status());
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+}
+
+// Sending two messages with the same collapse key and same app id while
+// disconnected should only send the latter of the two on reconnection.
+TEST_F(MCSClientTest, CollapseKeysSameApp) {
+ BuildMCSClient();
+ InitializeClient();
+ MCSMessage message(BuildDataMessage(
+ "from", "app", "message id 1", 1, "1", kTTLValue, 1, 0, "token", 0));
+ mcs_client()->SendMessage(message);
+
+ MCSMessage message2(BuildDataMessage(
+ "from", "app", "message id 2", 1, "1", kTTLValue, 1, 0, "token", 0));
+ mcs_client()->SendMessage(message2);
+
+ LoginClient(std::vector<std::string>());
+ GetFakeHandler()->ExpectOutgoingMessage(message2);
+ PumpLoop();
+}
+
+// Sending two messages with the same collapse key and different app id while
+// disconnected should not perform any collapsing.
+TEST_F(MCSClientTest, CollapseKeysDifferentApp) {
+ BuildMCSClient();
+ InitializeClient();
+ MCSMessage message(BuildDataMessage(
+ "from", "app", "message id 1", 1, "1", kTTLValue, 1, 0, "token", 0));
+ mcs_client()->SendMessage(message);
+
+ MCSMessage message2(BuildDataMessage("from",
+ "app 2",
+ "message id 2",
+ 1,
+ "2",
+ kTTLValue,
+ 1,
+ 0,
+ "token",
+ 0));
+ mcs_client()->SendMessage(message2);
+
+ LoginClient(std::vector<std::string>());
+ GetFakeHandler()->ExpectOutgoingMessage(message);
+ GetFakeHandler()->ExpectOutgoingMessage(message2);
+ PumpLoop();
+}
+
+// Sending two messages with the same collapse key and app id, but different
+// user, while disconnected, should not perform any collapsing.
+TEST_F(MCSClientTest, CollapseKeysDifferentUser) {
+ BuildMCSClient();
+ InitializeClient();
+ MCSMessage message(BuildDataMessage(
+ "from", "app", "message id 1", 1, "1", kTTLValue, 1, 0, "token", 0));
+ mcs_client()->SendMessage(message);
+
+ MCSMessage message2(BuildDataMessage("from",
+ "app",
+ "message id 2",
+ 1,
+ "2",
+ kTTLValue,
+ 1,
+ 0,
+ "token",
+ 1));
+ mcs_client()->SendMessage(message2);
+
+ LoginClient(std::vector<std::string>());
+ GetFakeHandler()->ExpectOutgoingMessage(message);
+ GetFakeHandler()->ExpectOutgoingMessage(message2);
+ PumpLoop();
}
} // namespace
diff --git a/chromium/google_apis/gcm/engine/registration_info.cc b/chromium/google_apis/gcm/engine/registration_info.cc
new file mode 100644
index 00000000000..6a41c76e00e
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/registration_info.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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 "google_apis/gcm/engine/registration_info.h"
+
+#include "base/strings/string_util.h"
+
+namespace gcm {
+
+RegistrationInfo::RegistrationInfo() {
+}
+
+RegistrationInfo::~RegistrationInfo() {
+}
+
+std::string RegistrationInfo::SerializeAsString() const {
+ if (sender_ids.empty() || registration_id.empty())
+ return std::string();
+
+ // Serialize as:
+ // sender1,sender2,...=reg_id
+ std::string value;
+ for (std::vector<std::string>::const_iterator iter = sender_ids.begin();
+ iter != sender_ids.end(); ++iter) {
+ DCHECK(!iter->empty() &&
+ iter->find(',') == std::string::npos &&
+ iter->find('=') == std::string::npos);
+ if (!value.empty())
+ value += ",";
+ value += *iter;
+ }
+
+ DCHECK(registration_id.find('=') == std::string::npos);
+ value += '=';
+ value += registration_id;
+ return value;
+}
+
+bool RegistrationInfo::ParseFromString(const std::string& value) {
+ if (value.empty())
+ return true;
+
+ size_t pos = value.find('=');
+ if (pos == std::string::npos)
+ return false;
+
+ std::string senders = value.substr(0, pos);
+ registration_id = value.substr(pos + 1);
+
+ Tokenize(senders, ",", &sender_ids);
+
+ if (sender_ids.empty() || registration_id.empty()) {
+ sender_ids.clear();
+ registration_id.clear();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/registration_info.h b/chromium/google_apis/gcm/engine/registration_info.h
new file mode 100644
index 00000000000..c06a6aa4c56
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/registration_info.h
@@ -0,0 +1,35 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_
+#define GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/linked_ptr.h"
+#include "google_apis/gcm/base/gcm_export.h"
+
+namespace gcm {
+
+struct GCM_EXPORT RegistrationInfo {
+ RegistrationInfo();
+ ~RegistrationInfo();
+
+ std::string SerializeAsString() const;
+ bool ParseFromString(const std::string& value);
+
+ std::vector<std::string> sender_ids;
+ std::string registration_id;
+};
+
+// Map of app id to registration info.
+typedef std::map<std::string, linked_ptr<RegistrationInfo> >
+RegistrationInfoMap;
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_
diff --git a/chromium/google_apis/gcm/engine/registration_request.cc b/chromium/google_apis/gcm/engine/registration_request.cc
new file mode 100644
index 00000000000..e72e7ba2b79
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/registration_request.cc
@@ -0,0 +1,267 @@
+// Copyright 2014 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 "google_apis/gcm/engine/registration_request.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
+#include "net/base/escape.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+namespace gcm {
+
+namespace {
+
+const char kRegistrationRequestContentType[] =
+ "application/x-www-form-urlencoded";
+
+// Request constants.
+const char kAppIdKey[] = "app";
+const char kDeviceIdKey[] = "device";
+const char kLoginHeader[] = "AidLogin";
+const char kSenderKey[] = "sender";
+
+// Request validation constants.
+const size_t kMaxSenders = 100;
+
+// Response constants.
+const char kErrorPrefix[] = "Error=";
+const char kTokenPrefix[] = "token=";
+const char kDeviceRegistrationError[] = "PHONE_REGISTRATION_ERROR";
+const char kAuthenticationFailed[] = "AUTHENTICATION_FAILED";
+const char kInvalidSender[] = "INVALID_SENDER";
+const char kInvalidParameters[] = "INVALID_PARAMETERS";
+
+void BuildFormEncoding(const std::string& key,
+ const std::string& value,
+ std::string* out) {
+ if (!out->empty())
+ out->append("&");
+ out->append(key + "=" + net::EscapeUrlEncodedData(value, true));
+}
+
+// Gets correct status from the error message.
+RegistrationRequest::Status GetStatusFromError(const std::string& error) {
+ // TODO(fgorski): Improve error parsing in case there is nore then just an
+ // Error=ERROR_STRING in response.
+ if (error.find(kDeviceRegistrationError) != std::string::npos)
+ return RegistrationRequest::DEVICE_REGISTRATION_ERROR;
+ if (error.find(kAuthenticationFailed) != std::string::npos)
+ return RegistrationRequest::AUTHENTICATION_FAILED;
+ if (error.find(kInvalidSender) != std::string::npos)
+ return RegistrationRequest::INVALID_SENDER;
+ if (error.find(kInvalidParameters) != std::string::npos)
+ return RegistrationRequest::INVALID_PARAMETERS;
+ return RegistrationRequest::UNKNOWN_ERROR;
+}
+
+// Indicates whether a retry attempt should be made based on the status of the
+// last request.
+bool ShouldRetryWithStatus(RegistrationRequest::Status status) {
+ return status == RegistrationRequest::UNKNOWN_ERROR ||
+ status == RegistrationRequest::AUTHENTICATION_FAILED ||
+ status == RegistrationRequest::DEVICE_REGISTRATION_ERROR ||
+ status == RegistrationRequest::HTTP_NOT_OK ||
+ status == RegistrationRequest::URL_FETCHING_FAILED ||
+ status == RegistrationRequest::RESPONSE_PARSING_FAILED;
+}
+
+void RecordRegistrationStatusToUMA(RegistrationRequest::Status status) {
+ UMA_HISTOGRAM_ENUMERATION("GCM.RegistrationRequestStatus", status,
+ RegistrationRequest::STATUS_COUNT);
+}
+
+} // namespace
+
+RegistrationRequest::RequestInfo::RequestInfo(
+ uint64 android_id,
+ uint64 security_token,
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids)
+ : android_id(android_id),
+ security_token(security_token),
+ app_id(app_id),
+ sender_ids(sender_ids) {
+}
+
+RegistrationRequest::RequestInfo::~RequestInfo() {}
+
+RegistrationRequest::RegistrationRequest(
+ const GURL& registration_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const RegistrationCallback& callback,
+ int max_retry_count,
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ GCMStatsRecorder* recorder)
+ : callback_(callback),
+ request_info_(request_info),
+ registration_url_(registration_url),
+ backoff_entry_(&backoff_policy),
+ request_context_getter_(request_context_getter),
+ retries_left_(max_retry_count),
+ recorder_(recorder),
+ weak_ptr_factory_(this) {
+ DCHECK_GE(max_retry_count, 0);
+}
+
+RegistrationRequest::~RegistrationRequest() {}
+
+void RegistrationRequest::Start() {
+ DCHECK(!callback_.is_null());
+ DCHECK(request_info_.android_id != 0UL);
+ DCHECK(request_info_.security_token != 0UL);
+ DCHECK(0 < request_info_.sender_ids.size() &&
+ request_info_.sender_ids.size() <= kMaxSenders);
+
+ DCHECK(!url_fetcher_.get());
+ url_fetcher_.reset(net::URLFetcher::Create(
+ registration_url_, net::URLFetcher::POST, this));
+ url_fetcher_->SetRequestContext(request_context_getter_);
+
+ std::string android_id = base::Uint64ToString(request_info_.android_id);
+ std::string auth_header =
+ std::string(net::HttpRequestHeaders::kAuthorization) + ": " +
+ kLoginHeader + " " + android_id + ":" +
+ base::Uint64ToString(request_info_.security_token);
+ url_fetcher_->SetExtraRequestHeaders(auth_header);
+
+ std::string body;
+ BuildFormEncoding(kAppIdKey, request_info_.app_id, &body);
+ BuildFormEncoding(kDeviceIdKey, android_id, &body);
+
+ std::string senders;
+ for (std::vector<std::string>::const_iterator iter =
+ request_info_.sender_ids.begin();
+ iter != request_info_.sender_ids.end();
+ ++iter) {
+ DCHECK(!iter->empty());
+ if (!senders.empty())
+ senders.append(",");
+ senders.append(*iter);
+ }
+ BuildFormEncoding(kSenderKey, senders, &body);
+ UMA_HISTOGRAM_COUNTS("GCM.RegistrationSenderIdCount",
+ request_info_.sender_ids.size());
+
+ DVLOG(1) << "Performing registration for: " << request_info_.app_id;
+ DVLOG(1) << "Registration request: " << body;
+ url_fetcher_->SetUploadData(kRegistrationRequestContentType, body);
+ recorder_->RecordRegistrationSent(request_info_.app_id, senders);
+ request_start_time_ = base::TimeTicks::Now();
+ url_fetcher_->Start();
+}
+
+void RegistrationRequest::RetryWithBackoff(bool update_backoff) {
+ if (update_backoff) {
+ DCHECK_GT(retries_left_, 0);
+ --retries_left_;
+ url_fetcher_.reset();
+ backoff_entry_.InformOfRequest(false);
+ }
+
+ if (backoff_entry_.ShouldRejectRequest()) {
+ DVLOG(1) << "Delaying GCM registration of app: "
+ << request_info_.app_id << ", for "
+ << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
+ << " milliseconds.";
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RegistrationRequest::RetryWithBackoff,
+ weak_ptr_factory_.GetWeakPtr(),
+ false),
+ backoff_entry_.GetTimeUntilRelease());
+ return;
+ }
+
+ Start();
+}
+
+RegistrationRequest::Status RegistrationRequest::ParseResponse(
+ const net::URLFetcher* source, std::string* token) {
+ if (!source->GetStatus().is_success()) {
+ LOG(ERROR) << "URL fetching failed.";
+ return URL_FETCHING_FAILED;
+ }
+
+ std::string response;
+ if (!source->GetResponseAsString(&response)) {
+ LOG(ERROR) << "Failed to parse registration response as a string.";
+ return RESPONSE_PARSING_FAILED;
+ }
+
+ if (source->GetResponseCode() == net::HTTP_OK) {
+ size_t token_pos = response.find(kTokenPrefix);
+ if (token_pos != std::string::npos) {
+ *token = response.substr(token_pos + arraysize(kTokenPrefix) - 1);
+ return SUCCESS;
+ }
+ }
+
+ // If we are able to parse a meaningful known error, let's do so. Some errors
+ // will have HTTP_BAD_REQUEST, some will have HTTP_OK response code.
+ size_t error_pos = response.find(kErrorPrefix);
+ if (error_pos != std::string::npos) {
+ std::string error = response.substr(
+ error_pos + arraysize(kErrorPrefix) - 1);
+ return GetStatusFromError(error);
+ }
+
+ // If we cannot tell what the error is, but at least we know response code was
+ // not OK.
+ if (source->GetResponseCode() != net::HTTP_OK) {
+ DLOG(ERROR) << "URL fetching HTTP response code is not OK. It is "
+ << source->GetResponseCode();
+ return HTTP_NOT_OK;
+ }
+
+ return UNKNOWN_ERROR;
+}
+
+void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) {
+ std::string token;
+ Status status = ParseResponse(source, &token);
+ RecordRegistrationStatusToUMA(status);
+ recorder_->RecordRegistrationResponse(
+ request_info_.app_id,
+ request_info_.sender_ids,
+ status);
+
+ if (ShouldRetryWithStatus(status)) {
+ if (retries_left_ > 0) {
+ recorder_->RecordRegistrationRetryRequested(
+ request_info_.app_id,
+ request_info_.sender_ids,
+ retries_left_);
+ RetryWithBackoff(true);
+ return;
+ }
+
+ status = REACHED_MAX_RETRIES;
+ recorder_->RecordRegistrationResponse(
+ request_info_.app_id,
+ request_info_.sender_ids,
+ status);
+ RecordRegistrationStatusToUMA(status);
+ }
+
+ if (status == SUCCESS) {
+ UMA_HISTOGRAM_COUNTS("GCM.RegistrationRetryCount",
+ backoff_entry_.failure_count());
+ UMA_HISTOGRAM_TIMES("GCM.RegistrationCompleteTime",
+ base::TimeTicks::Now() - request_start_time_);
+ }
+ callback_.Run(status, token);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/registration_request.h b/chromium/google_apis/gcm/engine/registration_request.h
new file mode 100644
index 00000000000..d41f2858f6b
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/registration_request.h
@@ -0,0 +1,128 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_REGISTRATION_REQUEST_H_
+#define GOOGLE_APIS_GCM_ENGINE_REGISTRATION_REQUEST_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gcm {
+
+class GCMStatsRecorder;
+
+// Registration request is used to obtain registration IDs for applications that
+// want to use GCM. It requires a set of parameters to be specified to identify
+// the Chrome instance, the user, the application and a set of senders that will
+// be authorized to address the application using it's assigned registration ID.
+class GCM_EXPORT RegistrationRequest : public net::URLFetcherDelegate {
+ public:
+ // This enum is also used in an UMA histogram (GCMRegistrationRequestStatus
+ // enum defined in tools/metrics/histograms/histogram.xml). Hence the entries
+ // here shouldn't be deleted or re-ordered and new ones should be added to
+ // the end.
+ enum Status {
+ SUCCESS, // Registration completed successfully.
+ INVALID_PARAMETERS, // One of request paramteres was invalid.
+ INVALID_SENDER, // One of the provided senders was invalid.
+ AUTHENTICATION_FAILED, // Authentication failed.
+ DEVICE_REGISTRATION_ERROR, // Chrome is not properly registered.
+ UNKNOWN_ERROR, // Unknown error.
+ URL_FETCHING_FAILED, // URL fetching failed.
+ HTTP_NOT_OK, // HTTP status was not OK.
+ RESPONSE_PARSING_FAILED, // Registration response parsing failed.
+ REACHED_MAX_RETRIES, // Reached maximum number of retries.
+ // NOTE: always keep this entry at the end. Add new status types only
+ // immediately above this line. Make sure to update the corresponding
+ // histogram enum accordingly.
+ STATUS_COUNT
+ };
+
+ // Callback completing the registration request.
+ typedef base::Callback<void(Status status,
+ const std::string& registration_id)>
+ RegistrationCallback;
+
+ // Details of the of the Registration Request. Only user's android ID and
+ // its serial number are optional and can be set to 0. All other parameters
+ // have to be specified to successfully complete the call.
+ struct GCM_EXPORT RequestInfo {
+ RequestInfo(uint64 android_id,
+ uint64 security_token,
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids);
+ ~RequestInfo();
+
+ // Android ID of the device.
+ uint64 android_id;
+ // Security token of the device.
+ uint64 security_token;
+ // Application ID.
+ std::string app_id;
+ // Certificate of the application.
+ std::string cert;
+ // List of IDs of senders. Allowed up to 100.
+ std::vector<std::string> sender_ids;
+ };
+
+ RegistrationRequest(
+ const GURL& registration_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const RegistrationCallback& callback,
+ int max_retry_count,
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ GCMStatsRecorder* recorder);
+ virtual ~RegistrationRequest();
+
+ void Start();
+
+ // URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ // Schedules a retry attempt, informs the backoff of a previous request's
+ // failure, when |update_backoff| is true.
+ void RetryWithBackoff(bool update_backoff);
+
+ // Parse the response returned by the URL fetcher into token, and returns the
+ // status.
+ Status ParseResponse(const net::URLFetcher* source, std::string* token);
+
+ RegistrationCallback callback_;
+ RequestInfo request_info_;
+ GURL registration_url_;
+
+ net::BackoffEntry backoff_entry_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ int retries_left_;
+ base::TimeTicks request_start_time_;
+
+ // Recorder that records GCM activities for debugging purpose. Not owned.
+ GCMStatsRecorder* recorder_;
+
+ base::WeakPtrFactory<RegistrationRequest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistrationRequest);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_REGISTRATION_REQUEST_H_
diff --git a/chromium/google_apis/gcm/engine/registration_request_unittest.cc b/chromium/google_apis/gcm/engine/registration_request_unittest.cc
new file mode 100644
index 00000000000..c5b8c767aed
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/registration_request_unittest.cc
@@ -0,0 +1,427 @@
+// Copyright 2014 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 <map>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "google_apis/gcm/engine/registration_request.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+const uint64 kAndroidId = 42UL;
+const char kAppId[] = "TestAppId";
+const char kDeveloperId[] = "Project1";
+const char kLoginHeader[] = "AidLogin";
+const char kRegistrationURL[] = "http://foo.bar/register";
+const uint64 kSecurityToken = 77UL;
+
+// Backoff policy for testing registration request.
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ // Explicitly set to 2 to skip the delay on the first retry, as we are not
+ // trying to test the backoff itself, but rather the fact that retry happens.
+ 2,
+
+ // Initial delay for exponential back-off in ms.
+ 15000, // 15 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.5, // 50%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 60 * 5, // 5 minutes.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+} // namespace
+
+class RegistrationRequestTest : public testing::Test {
+ public:
+ RegistrationRequestTest();
+ virtual ~RegistrationRequestTest();
+
+ void RegistrationCallback(RegistrationRequest::Status status,
+ const std::string& registration_id);
+
+ void CreateRequest(const std::string& sender_ids);
+ void SetResponseStatusAndString(net::HttpStatusCode status_code,
+ const std::string& response_body);
+ void CompleteFetch();
+ void set_max_retry_count(int max_retry_count) {
+ max_retry_count_ = max_retry_count;
+ }
+
+ protected:
+ int max_retry_count_;
+ RegistrationRequest::Status status_;
+ std::string registration_id_;
+ bool callback_called_;
+ std::map<std::string, std::string> extras_;
+ scoped_ptr<RegistrationRequest> request_;
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ scoped_refptr<net::TestURLRequestContextGetter> url_request_context_getter_;
+ FakeGCMStatsRecorder recorder_;
+};
+
+RegistrationRequestTest::RegistrationRequestTest()
+ : max_retry_count_(2),
+ status_(RegistrationRequest::SUCCESS),
+ callback_called_(false),
+ url_request_context_getter_(new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())) {}
+
+RegistrationRequestTest::~RegistrationRequestTest() {}
+
+void RegistrationRequestTest::RegistrationCallback(
+ RegistrationRequest::Status status,
+ const std::string& registration_id) {
+ status_ = status;
+ registration_id_ = registration_id;
+ callback_called_ = true;
+}
+
+void RegistrationRequestTest::CreateRequest(const std::string& sender_ids) {
+ std::vector<std::string> senders;
+ base::StringTokenizer tokenizer(sender_ids, ",");
+ while (tokenizer.GetNext())
+ senders.push_back(tokenizer.token());
+
+ request_.reset(new RegistrationRequest(
+ GURL(kRegistrationURL),
+ RegistrationRequest::RequestInfo(kAndroidId,
+ kSecurityToken,
+ kAppId,
+ senders),
+ kDefaultBackoffPolicy,
+ base::Bind(&RegistrationRequestTest::RegistrationCallback,
+ base::Unretained(this)),
+ max_retry_count_,
+ url_request_context_getter_.get(),
+ &recorder_));
+}
+
+void RegistrationRequestTest::SetResponseStatusAndString(
+ net::HttpStatusCode status_code,
+ const std::string& response_body) {
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(status_code);
+ fetcher->SetResponseString(response_body);
+}
+
+void RegistrationRequestTest::CompleteFetch() {
+ registration_id_.clear();
+ status_ = RegistrationRequest::SUCCESS;
+ callback_called_ = false;
+
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(RegistrationRequestTest, RequestSuccessful) {
+ set_max_retry_count(0);
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, RequestDataAndURL) {
+ CreateRequest(kDeveloperId);
+ request_->Start();
+
+ // Get data sent by request.
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ EXPECT_EQ(GURL(kRegistrationURL), fetcher->GetOriginalURL());
+
+ // Verify that authorization header was put together properly.
+ net::HttpRequestHeaders headers;
+ fetcher->GetExtraRequestHeaders(&headers);
+ std::string auth_header;
+ headers.GetHeader(net::HttpRequestHeaders::kAuthorization, &auth_header);
+ base::StringTokenizer auth_tokenizer(auth_header, " :");
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(kLoginHeader, auth_tokenizer.token());
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(base::Uint64ToString(kAndroidId), auth_tokenizer.token());
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(base::Uint64ToString(kSecurityToken), auth_tokenizer.token());
+
+ std::map<std::string, std::string> expected_pairs;
+ expected_pairs["app"] = kAppId;
+ expected_pairs["sender"] = kDeveloperId;
+ expected_pairs["device"] = base::Uint64ToString(kAndroidId);
+
+ // Verify data was formatted properly.
+ std::string upload_data = fetcher->upload_data();
+ base::StringTokenizer data_tokenizer(upload_data, "&=");
+ while (data_tokenizer.GetNext()) {
+ std::map<std::string, std::string>::iterator iter =
+ expected_pairs.find(data_tokenizer.token());
+ ASSERT_TRUE(iter != expected_pairs.end());
+ ASSERT_TRUE(data_tokenizer.GetNext());
+ EXPECT_EQ(iter->second, data_tokenizer.token());
+ // Ensure that none of the keys appears twice.
+ expected_pairs.erase(iter);
+ }
+
+ EXPECT_EQ(0UL, expected_pairs.size());
+}
+
+TEST_F(RegistrationRequestTest, RequestRegistrationWithMultipleSenderIds) {
+ CreateRequest("sender1,sender2@gmail.com");
+ request_->Start();
+
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ // Verify data was formatted properly.
+ std::string upload_data = fetcher->upload_data();
+ base::StringTokenizer data_tokenizer(upload_data, "&=");
+
+ // Skip all tokens until you hit entry for senders.
+ while (data_tokenizer.GetNext() && data_tokenizer.token() != "sender")
+ continue;
+
+ ASSERT_TRUE(data_tokenizer.GetNext());
+ std::string senders(net::UnescapeURLComponent(data_tokenizer.token(),
+ net::UnescapeRule::URL_SPECIAL_CHARS));
+ base::StringTokenizer sender_tokenizer(senders, ",");
+ ASSERT_TRUE(sender_tokenizer.GetNext());
+ EXPECT_EQ("sender1", sender_tokenizer.token());
+ ASSERT_TRUE(sender_tokenizer.GetNext());
+ EXPECT_EQ("sender2@gmail.com", sender_tokenizer.token());
+}
+
+TEST_F(RegistrationRequestTest, ResponseParsing) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseHttpStatusNotOK) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_UNAUTHORIZED, "token=2501");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseMissingRegistrationId) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, "some error in response");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ // Ensuring a retry happened and succeeds.
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseDeviceRegistrationError) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Error=PHONE_REGISTRATION_ERROR");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ // Ensuring a retry happened and succeeds.
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseAuthenticationError) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_UNAUTHORIZED,
+ "Error=AUTHENTICATION_FAILED");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ // Ensuring a retry happened and succeeds.
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseInvalidParameters) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Error=INVALID_PARAMETERS");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::INVALID_PARAMETERS, status_);
+ EXPECT_EQ(std::string(), registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseInvalidSender) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Error=INVALID_SENDER");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::INVALID_SENDER, status_);
+ EXPECT_EQ(std::string(), registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseInvalidSenderBadRequest) {
+ CreateRequest("sender1");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_BAD_REQUEST, "Error=INVALID_SENDER");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::INVALID_SENDER, status_);
+ EXPECT_EQ(std::string(), registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, RequestNotSuccessful) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ net::URLRequestStatus request_status(net::URLRequestStatus::FAILED, 1);
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_status(request_status);
+
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ // Ensuring a retry happened and succeeded.
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, ResponseHttpNotOk) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_GATEWAY_TIMEOUT, "token=2501");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ // Ensuring a retry happened and succeeded.
+ SetResponseStatusAndString(net::HTTP_OK, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::SUCCESS, status_);
+ EXPECT_EQ("2501", registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, MaximumAttemptsReachedWithZeroRetries) {
+ set_max_retry_count(0);
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_GATEWAY_TIMEOUT, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::REACHED_MAX_RETRIES, status_);
+ EXPECT_EQ(std::string(), registration_id_);
+}
+
+TEST_F(RegistrationRequestTest, MaximumAttemptsReached) {
+ CreateRequest("sender1,sender2");
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_GATEWAY_TIMEOUT, "token=2501");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_GATEWAY_TIMEOUT, "token=2501");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_GATEWAY_TIMEOUT, "token=2501");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(RegistrationRequest::REACHED_MAX_RETRIES, status_);
+ EXPECT_EQ(std::string(), registration_id_);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/rmq_store.cc b/chromium/google_apis/gcm/engine/rmq_store.cc
deleted file mode 100644
index eaa6dcc8059..00000000000
--- a/chromium/google_apis/gcm/engine/rmq_store.cc
+++ /dev/null
@@ -1,491 +0,0 @@
-// Copyright 2013 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 "google_apis/gcm/engine/rmq_store.h"
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/files/file_path.h"
-#include "base/logging.h"
-#include "base/message_loop/message_loop_proxy.h"
-#include "base/sequenced_task_runner.h"
-#include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
-#include "base/tracked_objects.h"
-#include "components/webdata/encryptor/encryptor.h"
-#include "google_apis/gcm/base/mcs_message.h"
-#include "google_apis/gcm/base/mcs_util.h"
-#include "google_apis/gcm/protocol/mcs.pb.h"
-#include "third_party/leveldatabase/src/include/leveldb/db.h"
-
-namespace gcm {
-
-namespace {
-
-// ---- LevelDB keys. ----
-// Key for this device's android id.
-const char kDeviceAIDKey[] = "device_aid_key";
-// Key for this device's android security token.
-const char kDeviceTokenKey[] = "device_token_key";
-// Lowest lexicographically ordered incoming message key.
-// Used for prefixing messages.
-const char kIncomingMsgKeyStart[] = "incoming1-";
-// Key guaranteed to be higher than all incoming message keys.
-// Used for limiting iteration.
-const char kIncomingMsgKeyEnd[] = "incoming2-";
-// Lowest lexicographically ordered outgoing message key.
-// Used for prefixing outgoing messages.
-const char kOutgoingMsgKeyStart[] = "outgoing1-";
-// Key guaranteed to be higher than all outgoing message keys.
-// Used for limiting iteration.
-const char kOutgoingMsgKeyEnd[] = "outgoing2-";
-
-std::string MakeIncomingKey(const std::string& persistent_id) {
- return kIncomingMsgKeyStart + persistent_id;
-}
-
-std::string MakeOutgoingKey(const std::string& persistent_id) {
- return kOutgoingMsgKeyStart + persistent_id;
-}
-
-std::string ParseOutgoingKey(const std::string& key) {
- return key.substr(arraysize(kOutgoingMsgKeyStart) - 1);
-}
-
-leveldb::Slice MakeSlice(const base::StringPiece& s) {
- return leveldb::Slice(s.begin(), s.size());
-}
-
-} // namespace
-
-class RMQStore::Backend : public base::RefCountedThreadSafe<RMQStore::Backend> {
- public:
- Backend(const base::FilePath& path,
- scoped_refptr<base::SequencedTaskRunner> foreground_runner);
-
- // Blocking implementations of RMQStore methods.
- void Load(const LoadCallback& callback);
- void Destroy(const UpdateCallback& callback);
- void SetDeviceCredentials(uint64 device_android_id,
- uint64 device_security_token,
- const UpdateCallback& callback);
- void AddIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback);
- void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback);
- void AddOutgoingMessage(const std::string& persistent_id,
- const MCSMessage& message,
- const UpdateCallback& callback);
- void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback);
-
- private:
- friend class base::RefCountedThreadSafe<Backend>;
- ~Backend();
-
- bool LoadDeviceCredentials(uint64* android_id, uint64* security_token);
- bool LoadIncomingMessages(std::vector<std::string>* incoming_messages);
- bool LoadOutgoingMessages(
- std::map<std::string, google::protobuf::MessageLite*>* outgoing_messages);
-
- const base::FilePath path_;
- scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_;
-
- scoped_ptr<leveldb::DB> db_;
-};
-
-RMQStore::Backend::Backend(
- const base::FilePath& path,
- scoped_refptr<base::SequencedTaskRunner> foreground_task_runner)
- : path_(path),
- foreground_task_runner_(foreground_task_runner) {
-}
-
-RMQStore::Backend::~Backend() {
-}
-
-void RMQStore::Backend::Load(const LoadCallback& callback) {
- LoadResult result;
-
- leveldb::Options options;
- options.create_if_missing = true;
- leveldb::DB* db;
- leveldb::Status status = leveldb::DB::Open(options,
- path_.AsUTF8Unsafe(),
- &db);
- if (!status.ok()) {
- LOG(ERROR) << "Failed to open database " << path_.value()
- << ": " << status.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, result));
- return;
- }
- db_.reset(db);
-
- if (!LoadDeviceCredentials(&result.device_android_id,
- &result.device_security_token) ||
- !LoadIncomingMessages(&result.incoming_messages) ||
- !LoadOutgoingMessages(&result.outgoing_messages)) {
- result.device_android_id = 0;
- result.device_security_token = 0;
- result.incoming_messages.clear();
- STLDeleteContainerPairSecondPointers(result.outgoing_messages.begin(),
- result.outgoing_messages.end());
- result.outgoing_messages.clear();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, result));
- return;
- }
-
- DVLOG(1) << "Succeeded in loading " << result.incoming_messages.size()
- << " unacknowledged incoming messages and "
- << result.outgoing_messages.size()
- << " unacknowledged outgoing messages.";
- result.success = true;
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, result));
- return;
-}
-
-void RMQStore::Backend::Destroy(const UpdateCallback& callback) {
- DVLOG(1) << "Destroying RMQ store.";
- const leveldb::Status s =
- leveldb::DestroyDB(path_.AsUTF8Unsafe(),
- leveldb::Options());
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "Destroy failed.";
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-}
-
-void RMQStore::Backend::SetDeviceCredentials(uint64 device_android_id,
- uint64 device_security_token,
- const UpdateCallback& callback) {
- DVLOG(1) << "Saving device credentials with AID " << device_android_id;
- leveldb::WriteOptions write_options;
- write_options.sync = true;
-
- std::string encrypted_token;
- Encryptor::EncryptString(base::Uint64ToString(device_security_token),
- &encrypted_token);
- leveldb::Status s =
- db_->Put(write_options,
- MakeSlice(kDeviceAIDKey),
- MakeSlice(base::Uint64ToString(device_android_id)));
- if (s.ok()) {
- s = db_->Put(write_options,
- MakeSlice(kDeviceTokenKey),
- MakeSlice(encrypted_token));
- }
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "LevelDB put failed: " << s.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-}
-
-void RMQStore::Backend::AddIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback) {
- DVLOG(1) << "Saving incoming message with id " << persistent_id;
- leveldb::WriteOptions write_options;
- write_options.sync = true;
-
- const leveldb::Status s =
- db_->Put(write_options,
- MakeSlice(MakeIncomingKey(persistent_id)),
- MakeSlice(persistent_id));
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "LevelDB put failed: " << s.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-}
-
-void RMQStore::Backend::RemoveIncomingMessages(
- const PersistentIdList& persistent_ids,
- const UpdateCallback& callback) {
- leveldb::WriteOptions write_options;
- write_options.sync = true;
-
- leveldb::Status s;
- for (PersistentIdList::const_iterator iter = persistent_ids.begin();
- iter != persistent_ids.end(); ++iter){
- DVLOG(1) << "Removing incoming message with id " << *iter;
- s = db_->Delete(write_options,
- MakeSlice(MakeIncomingKey(*iter)));
- if (!s.ok())
- break;
- }
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-}
-
-void RMQStore::Backend::AddOutgoingMessage(
- const std::string& persistent_id,
- const MCSMessage& message,
- const UpdateCallback& callback) {
- DVLOG(1) << "Saving outgoing message with id " << persistent_id;
- leveldb::WriteOptions write_options;
- write_options.sync = true;
-
- std::string data = static_cast<char>(message.tag()) +
- message.SerializeAsString();
- const leveldb::Status s =
- db_->Put(write_options,
- MakeSlice(MakeOutgoingKey(persistent_id)),
- MakeSlice(data));
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "LevelDB put failed: " << s.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-
-}
-
-void RMQStore::Backend::RemoveOutgoingMessages(
- const PersistentIdList& persistent_ids,
- const UpdateCallback& callback) {
- leveldb::WriteOptions write_options;
- write_options.sync = true;
-
- leveldb::Status s;
- for (PersistentIdList::const_iterator iter = persistent_ids.begin();
- iter != persistent_ids.end(); ++iter){
- DVLOG(1) << "Removing outgoing message with id " << *iter;
- s = db_->Delete(write_options,
- MakeSlice(MakeOutgoingKey(*iter)));
- if (!s.ok())
- break;
- }
- if (s.ok()) {
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, true));
- return;
- }
- LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
- foreground_task_runner_->PostTask(FROM_HERE,
- base::Bind(callback, false));
-}
-
-bool RMQStore::Backend::LoadDeviceCredentials(uint64* android_id,
- uint64* security_token) {
- leveldb::ReadOptions read_options;
- read_options.verify_checksums = true;
-
- std::string result;
- leveldb::Status s = db_->Get(read_options,
- MakeSlice(kDeviceAIDKey),
- &result);
- if (s.ok()) {
- if (!base::StringToUint64(result, android_id)) {
- LOG(ERROR) << "Failed to restore device id.";
- return false;
- }
- result.clear();
- s = db_->Get(read_options,
- MakeSlice(kDeviceTokenKey),
- &result);
- }
- if (s.ok()) {
- std::string decrypted_token;
- Encryptor::DecryptString(result, &decrypted_token);
- if (!base::StringToUint64(decrypted_token, security_token)) {
- LOG(ERROR) << "Failed to restore security token.";
- return false;
- }
- return true;
- }
-
- if (s.IsNotFound()) {
- DVLOG(1) << "No credentials found.";
- return true;
- }
-
- LOG(ERROR) << "Error reading credentials from store.";
- return false;
-}
-
-bool RMQStore::Backend::LoadIncomingMessages(
- std::vector<std::string>* incoming_messages) {
- leveldb::ReadOptions read_options;
- read_options.verify_checksums = true;
-
- scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
- for (iter->Seek(MakeSlice(kIncomingMsgKeyStart));
- iter->Valid() && iter->key().ToString() < kIncomingMsgKeyEnd;
- iter->Next()) {
- leveldb::Slice s = iter->value();
- if (s.empty()) {
- LOG(ERROR) << "Error reading incoming message with key "
- << iter->key().ToString();
- return false;
- }
- DVLOG(1) << "Found incoming message with id " << s.ToString();
- incoming_messages->push_back(s.ToString());
- }
-
- return true;
-}
-
-bool RMQStore::Backend::LoadOutgoingMessages(
- std::map<std::string, google::protobuf::MessageLite*>*
- outgoing_messages) {
- leveldb::ReadOptions read_options;
- read_options.verify_checksums = true;
-
- scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
- for (iter->Seek(MakeSlice(kOutgoingMsgKeyStart));
- iter->Valid() && iter->key().ToString() < kOutgoingMsgKeyEnd;
- iter->Next()) {
- leveldb::Slice s = iter->value();
- if (s.size() <= 1) {
- LOG(ERROR) << "Error reading incoming message with key " << s.ToString();
- return false;
- }
- uint8 tag = iter->value().data()[0];
- std::string id = ParseOutgoingKey(iter->key().ToString());
- scoped_ptr<google::protobuf::MessageLite> message(
- BuildProtobufFromTag(tag));
- if (!message.get() ||
- !message->ParseFromString(iter->value().ToString().substr(1))) {
- LOG(ERROR) << "Failed to parse outgoing message with id "
- << id << " and tag " << tag;
- return false;
- }
- DVLOG(1) << "Found outgoing message with id " << id << " of type "
- << base::IntToString(tag);
- (*outgoing_messages)[id] = message.release();
- }
-
- return true;
-}
-
-RMQStore::LoadResult::LoadResult()
- : success(false),
- device_android_id(0),
- device_security_token(0) {
-}
-RMQStore::LoadResult::~LoadResult() {}
-
-RMQStore::RMQStore(
- const base::FilePath& path,
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
- : backend_(new Backend(path, base::MessageLoopProxy::current())),
- blocking_task_runner_(blocking_task_runner) {
-}
-
-RMQStore::~RMQStore() {
-}
-
-void RMQStore::Load(const LoadCallback& callback) {
- blocking_task_runner_->PostTask(FROM_HERE,
- base::Bind(&RMQStore::Backend::Load,
- backend_,
- callback));
-}
-
-void RMQStore::Destroy(const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::Destroy,
- backend_,
- callback));
-}
-
-void RMQStore::SetDeviceCredentials(uint64 device_android_id,
- uint64 device_security_token,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::SetDeviceCredentials,
- backend_,
- device_android_id,
- device_security_token,
- callback));
-}
-
-void RMQStore::AddIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::AddIncomingMessage,
- backend_,
- persistent_id,
- callback));
-}
-
-void RMQStore::RemoveIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::RemoveIncomingMessages,
- backend_,
- PersistentIdList(1, persistent_id),
- callback));
-}
-
-void RMQStore::RemoveIncomingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::RemoveIncomingMessages,
- backend_,
- persistent_ids,
- callback));
-}
-
-void RMQStore::AddOutgoingMessage(const std::string& persistent_id,
- const MCSMessage& message,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::AddOutgoingMessage,
- backend_,
- persistent_id,
- message,
- callback));
-}
-
-void RMQStore::RemoveOutgoingMessage(const std::string& persistent_id,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::RemoveOutgoingMessages,
- backend_,
- PersistentIdList(1, persistent_id),
- callback));
-}
-
-void RMQStore::RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback) {
- blocking_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&RMQStore::Backend::RemoveOutgoingMessages,
- backend_,
- persistent_ids,
- callback));
-}
-
-} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/rmq_store.h b/chromium/google_apis/gcm/engine/rmq_store.h
deleted file mode 100644
index d3762a199c7..00000000000
--- a/chromium/google_apis/gcm/engine/rmq_store.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2013 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.
-
-#ifndef GOOGLE_APIS_GCM_ENGINE_RMQ_STORE_H_
-#define GOOGLE_APIS_GCM_ENGINE_RMQ_STORE_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "google_apis/gcm/base/gcm_export.h"
-
-namespace base {
-class FilePath;
-class SequencedTaskRunner;
-} // namespace base
-
-namespace google {
-namespace protobuf {
-class MessageLite;
-} // namespace protobuf
-} // namespace google
-
-namespace gcm {
-
-class MCSMessage;
-
-// A Reliable Message Queue store.
-// Will perform all blocking operations on the blocking task runner, and will
-// post all callbacks to the thread on which the RMQStore is created.
-class GCM_EXPORT RMQStore {
- public:
- // Container for Load(..) results.
- struct GCM_EXPORT LoadResult {
- LoadResult();
- ~LoadResult();
-
- bool success;
- uint64 device_android_id;
- uint64 device_security_token;
- std::vector<std::string> incoming_messages;
- std::map<std::string, google::protobuf::MessageLite*>
- outgoing_messages;
- };
-
- typedef std::vector<std::string> PersistentIdList;
- // Note: callee receives ownership of |outgoing_messages|' values.
- typedef base::Callback<void(const LoadResult& result)> LoadCallback;
- typedef base::Callback<void(bool success)> UpdateCallback;
-
- RMQStore(const base::FilePath& path,
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
- ~RMQStore();
-
- // Load the directory and pass the initial state back to caller.
- void Load(const LoadCallback& callback);
-
- // Clears the RMQ store of all data and destroys any LevelDB files associated
- // with this store.
- // WARNING: this will permanently destroy any pending outgoing messages
- // and require the device to re-create credentials.
- void Destroy(const UpdateCallback& callback);
-
- // Sets this device's messaging credentials.
- void SetDeviceCredentials(uint64 device_android_id,
- uint64 device_security_token,
- const UpdateCallback& callback);
-
- // Unacknowledged incoming message handling.
- void AddIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback);
- void RemoveIncomingMessage(const std::string& persistent_id,
- const UpdateCallback& callback);
- void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback);
-
- // Unacknowledged outgoing messages handling.
- // TODO(zea): implement per-app limits on the number of outgoing messages.
- void AddOutgoingMessage(const std::string& persistent_id,
- const MCSMessage& message,
- const UpdateCallback& callback);
- void RemoveOutgoingMessage(const std::string& persistent_id,
- const UpdateCallback& callback);
- void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
- const UpdateCallback& callback);
-
- private:
- class Backend;
-
- scoped_refptr<Backend> backend_;
- scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
-
- DISALLOW_COPY_AND_ASSIGN(RMQStore);
-};
-
-} // namespace gcm
-
-#endif // GOOGLE_APIS_GCM_ENGINE_RMQ_STORE_H_
diff --git a/chromium/google_apis/gcm/engine/rmq_store_unittest.cc b/chromium/google_apis/gcm/engine/rmq_store_unittest.cc
deleted file mode 100644
index 1fd55bcf14b..00000000000
--- a/chromium/google_apis/gcm/engine/rmq_store_unittest.cc
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2013 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 "google_apis/gcm/engine/rmq_store.h"
-
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "components/webdata/encryptor/encryptor.h"
-#include "google_apis/gcm/base/mcs_message.h"
-#include "google_apis/gcm/base/mcs_util.h"
-#include "google_apis/gcm/protocol/mcs.pb.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace gcm {
-
-namespace {
-
-// Number of persistent ids to use in tests.
-const int kNumPersistentIds = 10;
-
-const uint64 kDeviceId = 22;
-const uint64 kDeviceToken = 55;
-
-class RMQStoreTest : public testing::Test {
- public:
- RMQStoreTest();
- virtual ~RMQStoreTest();
-
- scoped_ptr<RMQStore> BuildRMQStore();
-
- std::string GetNextPersistentId();
-
- void PumpLoop();
-
- void LoadCallback(RMQStore::LoadResult* result_dst,
- const RMQStore::LoadResult& result);
- void UpdateCallback(bool success);
-
- private:
- base::MessageLoop message_loop_;
- base::ScopedTempDir temp_directory_;
- scoped_ptr<base::RunLoop> run_loop_;
-};
-
-RMQStoreTest::RMQStoreTest() {
- EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
- run_loop_.reset(new base::RunLoop());
-
- // On OSX, prevent the Keychain permissions popup during unit tests.
- #if defined(OS_MACOSX)
- Encryptor::UseMockKeychain(true);
- #endif
-}
-
-RMQStoreTest::~RMQStoreTest() {
-}
-
-scoped_ptr<RMQStore> RMQStoreTest::BuildRMQStore() {
- return scoped_ptr<RMQStore>(new RMQStore(temp_directory_.path(),
- message_loop_.message_loop_proxy()));
-}
-
-std::string RMQStoreTest::GetNextPersistentId() {
- return base::Uint64ToString(base::Time::Now().ToInternalValue());
-}
-
-void RMQStoreTest::PumpLoop() {
- message_loop_.RunUntilIdle();
-}
-
-void RMQStoreTest::LoadCallback(RMQStore::LoadResult* result_dst,
- const RMQStore::LoadResult& result) {
- ASSERT_TRUE(result.success);
- *result_dst = result;
- run_loop_->Quit();
- run_loop_.reset(new base::RunLoop());
-}
-
-void RMQStoreTest::UpdateCallback(bool success) {
- ASSERT_TRUE(success);
-}
-
-// Verify creating a new database and loading it.
-TEST_F(RMQStoreTest, LoadNew) {
- scoped_ptr<RMQStore> rmq_store(BuildRMQStore());
- RMQStore::LoadResult load_result;
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_EQ(0U, load_result.device_android_id);
- ASSERT_EQ(0U, load_result.device_security_token);
- ASSERT_TRUE(load_result.incoming_messages.empty());
- ASSERT_TRUE(load_result.outgoing_messages.empty());
-}
-
-TEST_F(RMQStoreTest, DeviceCredentials) {
- scoped_ptr<RMQStore> rmq_store(BuildRMQStore());
- RMQStore::LoadResult load_result;
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- rmq_store->SetDeviceCredentials(kDeviceId,
- kDeviceToken,
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
-
- rmq_store = BuildRMQStore().Pass();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_EQ(kDeviceId, load_result.device_android_id);
- ASSERT_EQ(kDeviceToken, load_result.device_security_token);
-}
-
-// Verify saving some incoming messages, reopening the directory, and then
-// removing those incoming messages.
-TEST_F(RMQStoreTest, IncomingMessages) {
- scoped_ptr<RMQStore> rmq_store(BuildRMQStore());
- RMQStore::LoadResult load_result;
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- std::vector<std::string> persistent_ids;
- for (int i = 0; i < kNumPersistentIds; ++i) {
- persistent_ids.push_back(GetNextPersistentId());
- rmq_store->AddIncomingMessage(persistent_ids.back(),
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
- }
-
- rmq_store = BuildRMQStore().Pass();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_EQ(persistent_ids, load_result.incoming_messages);
- ASSERT_TRUE(load_result.outgoing_messages.empty());
-
- rmq_store->RemoveIncomingMessages(persistent_ids,
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
-
- rmq_store = BuildRMQStore().Pass();
- load_result.incoming_messages.clear();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_TRUE(load_result.incoming_messages.empty());
- ASSERT_TRUE(load_result.outgoing_messages.empty());
-}
-
-// Verify saving some outgoing messages, reopening the directory, and then
-// removing those outgoing messages.
-TEST_F(RMQStoreTest, OutgoingMessages) {
- scoped_ptr<RMQStore> rmq_store(BuildRMQStore());
- RMQStore::LoadResult load_result;
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- std::vector<std::string> persistent_ids;
- const int kNumPersistentIds = 10;
- for (int i = 0; i < kNumPersistentIds; ++i) {
- persistent_ids.push_back(GetNextPersistentId());
- mcs_proto::DataMessageStanza message;
- message.set_from(persistent_ids.back());
- message.set_category(persistent_ids.back());
- rmq_store->AddOutgoingMessage(persistent_ids.back(),
- MCSMessage(message),
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
- }
-
- rmq_store = BuildRMQStore().Pass();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_TRUE(load_result.incoming_messages.empty());
- ASSERT_EQ(load_result.outgoing_messages.size(), persistent_ids.size());
- for (int i =0 ; i < kNumPersistentIds; ++i) {
- std::string id = persistent_ids[i];
- ASSERT_TRUE(load_result.outgoing_messages[id]);
- const mcs_proto::DataMessageStanza* message =
- reinterpret_cast<mcs_proto::DataMessageStanza *>(
- load_result.outgoing_messages[id]);
- ASSERT_EQ(message->from(), id);
- ASSERT_EQ(message->category(), id);
- }
-
- rmq_store->RemoveOutgoingMessages(persistent_ids,
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
-
- rmq_store = BuildRMQStore().Pass();
- load_result.outgoing_messages.clear();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_TRUE(load_result.incoming_messages.empty());
- ASSERT_TRUE(load_result.outgoing_messages.empty());
-}
-
-// Verify incoming and outgoing messages don't conflict.
-TEST_F(RMQStoreTest, IncomingAndOutgoingMessages) {
- scoped_ptr<RMQStore> rmq_store(BuildRMQStore());
- RMQStore::LoadResult load_result;
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- std::vector<std::string> persistent_ids;
- const int kNumPersistentIds = 10;
- for (int i = 0; i < kNumPersistentIds; ++i) {
- persistent_ids.push_back(GetNextPersistentId());
- rmq_store->AddIncomingMessage(persistent_ids.back(),
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
-
- mcs_proto::DataMessageStanza message;
- message.set_from(persistent_ids.back());
- message.set_category(persistent_ids.back());
- rmq_store->AddOutgoingMessage(persistent_ids.back(),
- MCSMessage(message),
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
- }
-
-
- rmq_store = BuildRMQStore().Pass();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_EQ(persistent_ids, load_result.incoming_messages);
- ASSERT_EQ(load_result.outgoing_messages.size(), persistent_ids.size());
- for (int i =0 ; i < kNumPersistentIds; ++i) {
- std::string id = persistent_ids[i];
- ASSERT_TRUE(load_result.outgoing_messages[id]);
- const mcs_proto::DataMessageStanza* message =
- reinterpret_cast<mcs_proto::DataMessageStanza *>(
- load_result.outgoing_messages[id]);
- ASSERT_EQ(message->from(), id);
- ASSERT_EQ(message->category(), id);
- }
-
- rmq_store->RemoveIncomingMessages(persistent_ids,
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
- rmq_store->RemoveOutgoingMessages(persistent_ids,
- base::Bind(&RMQStoreTest::UpdateCallback,
- base::Unretained(this)));
- PumpLoop();
-
- rmq_store = BuildRMQStore().Pass();
- load_result.incoming_messages.clear();
- load_result.outgoing_messages.clear();
- rmq_store->Load(base::Bind(&RMQStoreTest::LoadCallback,
- base::Unretained(this),
- &load_result));
- PumpLoop();
-
- ASSERT_TRUE(load_result.incoming_messages.empty());
- ASSERT_TRUE(load_result.outgoing_messages.empty());
-}
-
-} // namespace
-
-} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/unregistration_request.cc b/chromium/google_apis/gcm/engine/unregistration_request.cc
new file mode 100644
index 00000000000..2b8c97dadf1
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/unregistration_request.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 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 "google_apis/gcm/engine/unregistration_request.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
+#include "net/base/escape.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+namespace gcm {
+
+namespace {
+
+const char kRequestContentType[] = "application/x-www-form-urlencoded";
+
+// Request constants.
+const char kAppIdKey[] = "app";
+const char kDeleteKey[] = "delete";
+const char kDeleteValue[] = "true";
+const char kDeviceIdKey[] = "device";
+const char kLoginHeader[] = "AidLogin";
+const char kUnregistrationCallerKey[] = "gcm_unreg_caller";
+// We are going to set the value to "false" in order to forcefully unregister
+// the application.
+const char kUnregistrationCallerValue[] = "false";
+
+// Response constants.
+const char kDeletedPrefix[] = "deleted=";
+const char kErrorPrefix[] = "Error=";
+const char kInvalidParameters[] = "INVALID_PARAMETERS";
+
+
+void BuildFormEncoding(const std::string& key,
+ const std::string& value,
+ std::string* out) {
+ if (!out->empty())
+ out->append("&");
+ out->append(key + "=" + net::EscapeUrlEncodedData(value, true));
+}
+
+UnregistrationRequest::Status ParseFetcherResponse(
+ const net::URLFetcher* source,
+ std::string request_app_id) {
+ if (!source->GetStatus().is_success()) {
+ DVLOG(1) << "Fetcher failed";
+ return UnregistrationRequest::URL_FETCHING_FAILED;
+ }
+
+ net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>(
+ source->GetResponseCode());
+ if (response_status != net::HTTP_OK) {
+ DVLOG(1) << "HTTP Status code is not OK, but: " << response_status;
+ if (response_status == net::HTTP_SERVICE_UNAVAILABLE)
+ return UnregistrationRequest::SERVICE_UNAVAILABLE;
+ else if (response_status == net::HTTP_INTERNAL_SERVER_ERROR)
+ return UnregistrationRequest::INTERNAL_SERVER_ERROR;
+ return UnregistrationRequest::HTTP_NOT_OK;
+ }
+
+ std::string response;
+ if (!source->GetResponseAsString(&response)) {
+ DVLOG(1) << "Failed to get response body.";
+ return UnregistrationRequest::NO_RESPONSE_BODY;
+ }
+
+ DVLOG(1) << "Parsing unregistration response.";
+ if (response.find(kDeletedPrefix) != std::string::npos) {
+ std::string app_id = response.substr(
+ response.find(kDeletedPrefix) + arraysize(kDeletedPrefix) - 1);
+ if (app_id == request_app_id)
+ return UnregistrationRequest::SUCCESS;
+ return UnregistrationRequest::INCORRECT_APP_ID;
+ }
+
+ if (response.find(kErrorPrefix) != std::string::npos) {
+ std::string error = response.substr(
+ response.find(kErrorPrefix) + arraysize(kErrorPrefix) - 1);
+ if (error == kInvalidParameters)
+ return UnregistrationRequest::INVALID_PARAMETERS;
+ return UnregistrationRequest::UNKNOWN_ERROR;
+ }
+
+ DVLOG(1) << "Not able to parse a meaningful output from response body."
+ << response;
+ return UnregistrationRequest::RESPONSE_PARSING_FAILED;
+}
+
+} // namespace
+
+UnregistrationRequest::RequestInfo::RequestInfo(
+ uint64 android_id,
+ uint64 security_token,
+ const std::string& app_id)
+ : android_id(android_id),
+ security_token(security_token),
+ app_id(app_id) {
+}
+
+UnregistrationRequest::RequestInfo::~RequestInfo() {}
+
+UnregistrationRequest::UnregistrationRequest(
+ const GURL& registration_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const UnregistrationCallback& callback,
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ GCMStatsRecorder* recorder)
+ : callback_(callback),
+ request_info_(request_info),
+ registration_url_(registration_url),
+ backoff_entry_(&backoff_policy),
+ request_context_getter_(request_context_getter),
+ recorder_(recorder),
+ weak_ptr_factory_(this) {
+}
+
+UnregistrationRequest::~UnregistrationRequest() {}
+
+void UnregistrationRequest::Start() {
+ DCHECK(!callback_.is_null());
+ DCHECK(request_info_.android_id != 0UL);
+ DCHECK(request_info_.security_token != 0UL);
+ DCHECK(!url_fetcher_.get());
+
+ url_fetcher_.reset(net::URLFetcher::Create(
+ registration_url_, net::URLFetcher::POST, this));
+ url_fetcher_->SetRequestContext(request_context_getter_);
+
+ std::string android_id = base::Uint64ToString(request_info_.android_id);
+ std::string auth_header =
+ std::string(kLoginHeader) + " " + android_id + ":" +
+ base::Uint64ToString(request_info_.security_token);
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kAuthorization, auth_header);
+ headers.SetHeader(kAppIdKey, request_info_.app_id);
+ url_fetcher_->SetExtraRequestHeaders(headers.ToString());
+
+ std::string body;
+ BuildFormEncoding(kAppIdKey, request_info_.app_id, &body);
+ BuildFormEncoding(kDeviceIdKey, android_id, &body);
+ BuildFormEncoding(kDeleteKey, kDeleteValue, &body);
+ BuildFormEncoding(kUnregistrationCallerKey,
+ kUnregistrationCallerValue,
+ &body);
+
+ DVLOG(1) << "Unregistration request: " << body;
+ url_fetcher_->SetUploadData(kRequestContentType, body);
+
+ DVLOG(1) << "Performing unregistration for: " << request_info_.app_id;
+ recorder_->RecordUnregistrationSent(request_info_.app_id);
+ request_start_time_ = base::TimeTicks::Now();
+ url_fetcher_->Start();
+}
+
+void UnregistrationRequest::RetryWithBackoff(bool update_backoff) {
+ if (update_backoff) {
+ url_fetcher_.reset();
+ backoff_entry_.InformOfRequest(false);
+ }
+
+ if (backoff_entry_.ShouldRejectRequest()) {
+ DVLOG(1) << "Delaying GCM unregistration of app: "
+ << request_info_.app_id << ", for "
+ << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
+ << " milliseconds.";
+ recorder_->RecordUnregistrationRetryDelayed(
+ request_info_.app_id,
+ backoff_entry_.GetTimeUntilRelease().InMilliseconds());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnregistrationRequest::RetryWithBackoff,
+ weak_ptr_factory_.GetWeakPtr(),
+ false),
+ backoff_entry_.GetTimeUntilRelease());
+ return;
+ }
+
+ Start();
+}
+
+void UnregistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) {
+ UnregistrationRequest::Status status =
+ ParseFetcherResponse(source, request_info_.app_id);
+
+ DVLOG(1) << "UnregistrationRequestStauts: " << status;
+ UMA_HISTOGRAM_ENUMERATION("GCM.UnregistrationRequestStatus",
+ status,
+ UNREGISTRATION_STATUS_COUNT);
+ recorder_->RecordUnregistrationResponse(request_info_.app_id, status);
+
+ if (status == URL_FETCHING_FAILED ||
+ status == SERVICE_UNAVAILABLE ||
+ status == INTERNAL_SERVER_ERROR ||
+ status == INCORRECT_APP_ID ||
+ status == RESPONSE_PARSING_FAILED) {
+ RetryWithBackoff(true);
+ return;
+ }
+
+ // status == SUCCESS || HTTP_NOT_OK || NO_RESPONSE_BODY ||
+ // INVALID_PARAMETERS || UNKNOWN_ERROR
+
+ if (status == SUCCESS) {
+ UMA_HISTOGRAM_COUNTS("GCM.UnregistrationRetryCount",
+ backoff_entry_.failure_count());
+ UMA_HISTOGRAM_TIMES("GCM.UnregistrationCompleteTime",
+ base::TimeTicks::Now() - request_start_time_);
+ }
+
+ callback_.Run(status);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/engine/unregistration_request.h b/chromium/google_apis/gcm/engine/unregistration_request.h
new file mode 100644
index 00000000000..aec3331d18a
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/unregistration_request.h
@@ -0,0 +1,115 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_ENGINE_UNREGISTRATION_REQUEST_H_
+#define GOOGLE_APIS_GCM_ENGINE_UNREGISTRATION_REQUEST_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace gcm {
+
+class GCMStatsRecorder;
+
+// Unregistration request is used to revoke registration IDs for applications
+// that were uninstalled and should no longer receive GCM messages. In case an
+// attempt to unregister fails, it will retry using the backoff policy.
+// TODO(fgorski): Consider sharing code with RegistrationRequest if possible.
+class GCM_EXPORT UnregistrationRequest : public net::URLFetcherDelegate {
+ public:
+ // Outcome of the response parsing. Note that these enums are consumed by a
+ // histogram, so ordering should not be modified.
+ enum Status {
+ SUCCESS, // Unregistration completed successfully.
+ URL_FETCHING_FAILED, // URL fetching failed.
+ NO_RESPONSE_BODY, // No response body.
+ RESPONSE_PARSING_FAILED, // Failed to parse a meaningful output from
+ // response
+ // body.
+ INCORRECT_APP_ID, // App ID returned by the fetcher does not match
+ // request.
+ INVALID_PARAMETERS, // Request parameters were invalid.
+ SERVICE_UNAVAILABLE, // Unregistration service unavailable.
+ INTERNAL_SERVER_ERROR, // Internal server error happened during request.
+ HTTP_NOT_OK, // HTTP response code was not OK.
+ UNKNOWN_ERROR, // Unknown error.
+ // NOTE: Always keep this entry at the end. Add new status types only
+ // immediately above this line. Make sure to update the corresponding
+ // histogram enum accordingly.
+ UNREGISTRATION_STATUS_COUNT,
+ };
+
+ // Callback completing the unregistration request.
+ typedef base::Callback<void(Status success)> UnregistrationCallback;
+
+ // Details of the of the Unregistration Request. All parameters are mandatory.
+ struct GCM_EXPORT RequestInfo {
+ RequestInfo(uint64 android_id,
+ uint64 security_token,
+ const std::string& app_id);
+ ~RequestInfo();
+
+ // Android ID of the device.
+ uint64 android_id;
+ // Security token of the device.
+ uint64 security_token;
+ // Application ID.
+ std::string app_id;
+ };
+
+ // Creates an instance of UnregistrationRequest. |callback| will be called
+ // once registration has been revoked or there has been an error that makes
+ // further retries pointless.
+ UnregistrationRequest(
+ const GURL& registration_url,
+ const RequestInfo& request_info,
+ const net::BackoffEntry::Policy& backoff_policy,
+ const UnregistrationCallback& callback,
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ GCMStatsRecorder* recorder);
+ virtual ~UnregistrationRequest();
+
+ // Starts an unregistration request.
+ void Start();
+
+ // URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ // Schedules a retry attempt and informs the backoff of previous request's
+ // failure, when |update_backoff| is true.
+ void RetryWithBackoff(bool update_backoff);
+
+ UnregistrationCallback callback_;
+ RequestInfo request_info_;
+ GURL registration_url_;
+
+ net::BackoffEntry backoff_entry_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ base::TimeTicks request_start_time_;
+
+ // Recorder that records GCM activities for debugging purpose. Not owned.
+ GCMStatsRecorder* recorder_;
+
+ base::WeakPtrFactory<UnregistrationRequest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnregistrationRequest);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_ENGINE_UNREGISTRATION_REQUEST_H_
diff --git a/chromium/google_apis/gcm/engine/unregistration_request_unittest.cc b/chromium/google_apis/gcm/engine/unregistration_request_unittest.cc
new file mode 100644
index 00000000000..247150390ae
--- /dev/null
+++ b/chromium/google_apis/gcm/engine/unregistration_request_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright 2014 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 <map>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "google_apis/gcm/engine/unregistration_request.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+const uint64 kAndroidId = 42UL;
+const char kLoginHeader[] = "AidLogin";
+const char kAppId[] = "TestAppId";
+const char kDeletedAppId[] = "deleted=TestAppId";
+const char kRegistrationURL[] = "http://foo.bar/register";
+const uint64 kSecurityToken = 77UL;
+
+// Backoff policy for testing registration request.
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ // Explicitly set to 2 to skip the delay on the first retry, as we are not
+ // trying to test the backoff itself, but rather the fact that retry happens.
+ 1,
+
+ // Initial delay for exponential back-off in ms.
+ 15000, // 15 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.5, // 50%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 60 * 5, // 5 minutes.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+} // namespace
+
+class UnregistrationRequestTest : public testing::Test {
+ public:
+ UnregistrationRequestTest();
+ virtual ~UnregistrationRequestTest();
+
+ void UnregistrationCallback(UnregistrationRequest::Status status);
+
+ void CreateRequest();
+ void SetResponseStatusAndString(net::HttpStatusCode status_code,
+ const std::string& response_body);
+ void CompleteFetch();
+
+ protected:
+ bool callback_called_;
+ UnregistrationRequest::Status status_;
+ scoped_ptr<UnregistrationRequest> request_;
+ base::MessageLoop message_loop_;
+ net::TestURLFetcherFactory url_fetcher_factory_;
+ scoped_refptr<net::TestURLRequestContextGetter> url_request_context_getter_;
+ FakeGCMStatsRecorder recorder_;
+};
+
+UnregistrationRequestTest::UnregistrationRequestTest()
+ : callback_called_(false),
+ status_(UnregistrationRequest::UNREGISTRATION_STATUS_COUNT),
+ url_request_context_getter_(new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())) {}
+
+UnregistrationRequestTest::~UnregistrationRequestTest() {}
+
+void UnregistrationRequestTest::UnregistrationCallback(
+ UnregistrationRequest::Status status) {
+ callback_called_ = true;
+ status_ = status;
+}
+
+void UnregistrationRequestTest::CreateRequest() {
+ request_.reset(new UnregistrationRequest(
+ GURL(kRegistrationURL),
+ UnregistrationRequest::RequestInfo(kAndroidId,
+ kSecurityToken,
+ kAppId),
+ kDefaultBackoffPolicy,
+ base::Bind(&UnregistrationRequestTest::UnregistrationCallback,
+ base::Unretained(this)),
+ url_request_context_getter_.get(),
+ &recorder_));
+}
+
+void UnregistrationRequestTest::SetResponseStatusAndString(
+ net::HttpStatusCode status_code,
+ const std::string& response_body) {
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(status_code);
+ fetcher->SetResponseString(response_body);
+}
+
+void UnregistrationRequestTest::CompleteFetch() {
+ status_ = UnregistrationRequest::UNREGISTRATION_STATUS_COUNT;
+ callback_called_ = false;
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(UnregistrationRequestTest, RequestDataPassedToFetcher) {
+ CreateRequest();
+ request_->Start();
+
+ // Get data sent by request.
+ net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+
+ EXPECT_EQ(GURL(kRegistrationURL), fetcher->GetOriginalURL());
+
+ // Verify that authorization header was put together properly.
+ net::HttpRequestHeaders headers;
+ fetcher->GetExtraRequestHeaders(&headers);
+ std::string auth_header;
+ headers.GetHeader(net::HttpRequestHeaders::kAuthorization, &auth_header);
+ base::StringTokenizer auth_tokenizer(auth_header, " :");
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(kLoginHeader, auth_tokenizer.token());
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(base::Uint64ToString(kAndroidId), auth_tokenizer.token());
+ ASSERT_TRUE(auth_tokenizer.GetNext());
+ EXPECT_EQ(base::Uint64ToString(kSecurityToken), auth_tokenizer.token());
+ std::string app_id_header;
+ headers.GetHeader("app", &app_id_header);
+ EXPECT_EQ(kAppId, app_id_header);
+
+ std::map<std::string, std::string> expected_pairs;
+ expected_pairs["app"] = kAppId;
+ expected_pairs["device"] = base::Uint64ToString(kAndroidId);
+ expected_pairs["delete"] = "true";
+ expected_pairs["gcm_unreg_caller"] = "false";
+
+ // Verify data was formatted properly.
+ std::string upload_data = fetcher->upload_data();
+ base::StringTokenizer data_tokenizer(upload_data, "&=");
+ while (data_tokenizer.GetNext()) {
+ std::map<std::string, std::string>::iterator iter =
+ expected_pairs.find(data_tokenizer.token());
+ ASSERT_TRUE(iter != expected_pairs.end()) << data_tokenizer.token();
+ ASSERT_TRUE(data_tokenizer.GetNext()) << data_tokenizer.token();
+ EXPECT_EQ(iter->second, data_tokenizer.token());
+ // Ensure that none of the keys appears twice.
+ expected_pairs.erase(iter);
+ }
+
+ EXPECT_EQ(0UL, expected_pairs.size());
+}
+
+TEST_F(UnregistrationRequestTest, SuccessfulUnregistration) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, ResponseHttpStatusNotOK) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_UNAUTHORIZED, "");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::HTTP_NOT_OK, status_);
+}
+
+TEST_F(UnregistrationRequestTest, ResponseEmpty) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, InvalidParametersError) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Error=INVALID_PARAMETERS");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::INVALID_PARAMETERS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, UnkwnownError) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "Error=XXX");
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::UNKNOWN_ERROR, status_);
+}
+
+TEST_F(UnregistrationRequestTest, ServiceUnavailable) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_SERVICE_UNAVAILABLE, "");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, InternalServerError) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_INTERNAL_SERVER_ERROR, "");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, IncorrectAppId) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "deleted=OtherTestAppId");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+TEST_F(UnregistrationRequestTest, ResponseParsingFailed) {
+ CreateRequest();
+ request_->Start();
+
+ SetResponseStatusAndString(net::HTTP_OK, "some malformed response");
+ CompleteFetch();
+
+ EXPECT_FALSE(callback_called_);
+
+ SetResponseStatusAndString(net::HTTP_OK, kDeletedAppId);
+ CompleteFetch();
+
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(UnregistrationRequest::SUCCESS, status_);
+}
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/gcm.gyp b/chromium/google_apis/gcm/gcm.gyp
index f81c4ef4493..81dcf21596f 100644
--- a/chromium/google_apis/gcm/gcm.gyp
+++ b/chromium/google_apis/gcm/gcm.gyp
@@ -31,35 +31,47 @@
'dependencies': [
'../../base/base.gyp:base',
'../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
- '../../components/components.gyp:encryptor',
'../../net/net.gyp:net',
'../../third_party/leveldatabase/leveldatabase.gyp:leveldatabase',
'../../third_party/protobuf/protobuf.gyp:protobuf_lite',
'../../url/url.gyp:url_lib',
],
'sources': [
- 'base/mcs_message.h',
'base/mcs_message.cc',
- 'base/mcs_util.h',
+ 'base/mcs_message.h',
'base/mcs_util.cc',
- 'base/socket_stream.h',
+ 'base/mcs_util.h',
'base/socket_stream.cc',
- 'engine/connection_factory.h',
+ 'base/socket_stream.h',
+ 'engine/checkin_request.cc',
+ 'engine/checkin_request.h',
'engine/connection_factory.cc',
- 'engine/connection_factory_impl.h',
+ 'engine/connection_factory.h',
'engine/connection_factory_impl.cc',
- 'engine/connection_handler.h',
+ 'engine/connection_factory_impl.h',
'engine/connection_handler.cc',
- 'engine/connection_handler_impl.h',
+ 'engine/connection_handler.h',
'engine/connection_handler_impl.cc',
- 'engine/mcs_client.h',
+ 'engine/connection_handler_impl.h',
+ 'engine/gcm_store.cc',
+ 'engine/gcm_store.h',
+ 'engine/gcm_store_impl.cc',
+ 'engine/gcm_store_impl.h',
+ 'engine/gservices_settings.cc',
+ 'engine/gservices_settings.h',
+ 'engine/heartbeat_manager.cc',
+ 'engine/heartbeat_manager.h',
'engine/mcs_client.cc',
- 'engine/rmq_store.h',
- 'engine/rmq_store.cc',
- 'gcm_client.cc',
- 'gcm_client.h',
- 'gcm_client_impl.cc',
- 'gcm_client_impl.h',
+ 'engine/mcs_client.h',
+ 'engine/registration_info.cc',
+ 'engine/registration_info.h',
+ 'engine/registration_request.cc',
+ 'engine/registration_request.h',
+ 'engine/unregistration_request.cc',
+ 'engine/unregistration_request.h',
+ 'monitoring/gcm_stats_recorder.h',
+ 'protocol/android_checkin.proto',
+ 'protocol/checkin.proto',
'protocol/mcs.proto',
],
'includes': [
@@ -67,6 +79,34 @@
],
},
+ # The test support library that is needed to test gcm.
+ {
+ 'target_name': 'gcm_test_support',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '..',
+ ],
+ 'export_dependent_settings': [
+ '../../third_party/protobuf/protobuf.gyp:protobuf_lite'
+ ],
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../testing/gtest.gyp:gtest',
+ '../../third_party/protobuf/protobuf.gyp:protobuf_lite',
+ 'gcm',
+ ],
+ 'sources': [
+ 'base/fake_encryptor.cc',
+ 'base/fake_encryptor.h',
+ 'engine/fake_connection_factory.cc',
+ 'engine/fake_connection_factory.h',
+ 'engine/fake_connection_handler.cc',
+ 'engine/fake_connection_handler.h',
+ 'monitoring/fake_gcm_stats_recorder.cc',
+ 'monitoring/fake_gcm_stats_recorder.h',
+ ],
+ },
+
# A standalone MCS (mobile connection server) client.
{
'target_name': 'mcs_probe',
@@ -80,7 +120,8 @@
'../../net/net.gyp:net',
'../../net/net.gyp:net_test_support',
'../../third_party/protobuf/protobuf.gyp:protobuf_lite',
- 'gcm'
+ 'gcm',
+ 'gcm_test_support'
],
'sources': [
'tools/mcs_probe.cc',
@@ -101,25 +142,27 @@
'dependencies': [
'../../base/base.gyp:run_all_unittests',
'../../base/base.gyp:base',
- '../../components/components.gyp:encryptor',
'../../net/net.gyp:net',
'../../net/net.gyp:net_test_support',
'../../testing/gtest.gyp:gtest',
'../../third_party/protobuf/protobuf.gyp:protobuf_lite',
- 'gcm'
+ 'mcs_probe',
+ 'gcm',
+ 'gcm_test_support'
],
'sources': [
'base/mcs_message_unittest.cc',
'base/mcs_util_unittest.cc',
'base/socket_stream_unittest.cc',
+ 'engine/checkin_request_unittest.cc',
'engine/connection_factory_impl_unittest.cc',
'engine/connection_handler_impl_unittest.cc',
- 'engine/fake_connection_factory.h',
- 'engine/fake_connection_factory.cc',
- 'engine/fake_connection_handler.h',
- 'engine/fake_connection_handler.cc',
+ 'engine/gcm_store_impl_unittest.cc',
+ 'engine/gservices_settings_unittest.cc',
+ 'engine/heartbeat_manager_unittest.cc',
'engine/mcs_client_unittest.cc',
- 'engine/rmq_store_unittest.cc',
+ 'engine/registration_request_unittest.cc',
+ 'engine/unregistration_request_unittest.cc',
]
},
],
diff --git a/chromium/google_apis/gcm/gcm_client.cc b/chromium/google_apis/gcm/gcm_client.cc
deleted file mode 100644
index e437a305e47..00000000000
--- a/chromium/google_apis/gcm/gcm_client.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2013 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 "google_apis/gcm/gcm_client.h"
-
-#include "base/lazy_instance.h"
-#include "google_apis/gcm/gcm_client_impl.h"
-
-namespace gcm {
-
-namespace {
-
-static base::LazyInstance<GCMClientImpl>::Leaky g_gcm_client =
- LAZY_INSTANCE_INITIALIZER;
-static GCMClient* g_gcm_client_override = NULL;
-
-} // namespace
-
-GCMClient::OutgoingMessage::OutgoingMessage()
- : time_to_live(0) {
-}
-
-GCMClient::OutgoingMessage::~OutgoingMessage() {
-}
-
-GCMClient::IncomingMessage::IncomingMessage() {
-}
-
-GCMClient::IncomingMessage::~IncomingMessage() {
-}
-
-// static
-GCMClient* GCMClient::Get() {
- if (g_gcm_client_override)
- return g_gcm_client_override;
- return g_gcm_client.Pointer();
-}
-
-// static
-void GCMClient::SetForTesting(GCMClient* client) {
- g_gcm_client_override = client;
-}
-
-} // namespace gcm
diff --git a/chromium/google_apis/gcm/gcm_client.h b/chromium/google_apis/gcm/gcm_client.h
deleted file mode 100644
index 66e17a04489..00000000000
--- a/chromium/google_apis/gcm/gcm_client.h
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2013 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.
-
-#ifndef GOOGLE_APIS_GCM_GCM_CLIENT_H_
-#define GOOGLE_APIS_GCM_GCM_CLIENT_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "google_apis/gcm/base/gcm_export.h"
-
-namespace base {
-class TaskRunner;
-}
-
-namespace gcm {
-
-// Interface that encapsulates the network communications with the Google Cloud
-// Messaging server. This interface is not supposed to be thread-safe.
-class GCM_EXPORT GCMClient {
- public:
- enum Result {
- // Successful operation.
- SUCCESS,
- // Invalid parameter.
- INVALID_PARAMETER,
- // Previous asynchronous operation is still pending to finish. Certain
- // operation, like register, is only allowed one at a time.
- ASYNC_OPERATION_PENDING,
- // Network socket error.
- NETWORK_ERROR,
- // Problem at the server.
- SERVER_ERROR,
- // Exceeded the specified TTL during message sending.
- TTL_EXCEEDED,
- // Other errors.
- UNKNOWN_ERROR
- };
-
- // Message data consisting of key-value pairs.
- typedef std::map<std::string, std::string> MessageData;
-
- // Message to be delivered to the other party.
- struct GCM_EXPORT OutgoingMessage {
- OutgoingMessage();
- ~OutgoingMessage();
-
- // Message ID.
- std::string id;
- // In seconds.
- int time_to_live;
- MessageData data;
- };
-
- // Message being received from the other party.
- struct GCM_EXPORT IncomingMessage {
- IncomingMessage();
- ~IncomingMessage();
-
- MessageData data;
- };
-
- // The check-in info for the user. Returned by the server.
- struct GCM_EXPORT CheckInInfo {
- CheckInInfo() : android_id(0), secret(0) {}
- bool IsValid() const { return android_id != 0 && secret != 0; }
- void Reset() {
- android_id = 0;
- secret = 0;
- }
-
- uint64 android_id;
- uint64 secret;
- };
-
- // A delegate interface that allows the GCMClient instance to interact with
- // its caller, i.e. notifying asynchronous event.
- class Delegate {
- public:
- // Called when the user has been checked in successfully or an error occurs.
- // |checkin_info|: valid if the checkin completed successfully.
- // |result|: the type of the error if an error occured, success otherwise.
- virtual void OnCheckInFinished(const CheckInInfo& checkin_info,
- Result result) = 0;
-
- // Called when the registration completed successfully or an error occurs.
- // |app_id|: application ID.
- // |registration_id|: non-empty if the registration completed successfully.
- // |result|: the type of the error if an error occured, success otherwise.
- virtual void OnRegisterFinished(const std::string& app_id,
- const std::string& registration_id,
- Result result) = 0;
-
- // Called when the message is scheduled to send successfully or an error
- // occurs.
- // |app_id|: application ID.
- // |message_id|: ID of the message being sent.
- // |result|: the type of the error if an error occured, success otherwise.
- virtual void OnSendFinished(const std::string& app_id,
- const std::string& message_id,
- Result result) = 0;
-
- // Called when a message has been received.
- // |app_id|: application ID.
- // |message|: message received.
- virtual void OnMessageReceived(const std::string& app_id,
- const IncomingMessage& message) = 0;
-
- // Called when some messages have been deleted from the server.
- // |app_id|: application ID.
- virtual void OnMessagesDeleted(const std::string& app_id) = 0;
-
- // Called when a message failed to send to the server.
- // |app_id|: application ID.
- // |message_id|: ID of the message being sent.
- // |result|: the type of the error if an error occured, success otherwise.
- virtual void OnMessageSendError(const std::string& app_id,
- const std::string& message_id,
- Result result) = 0;
-
- // Returns the checkin info associated with this user. The delegate class
- // is expected to persist the checkin info that is provided by
- // OnCheckInFinished.
- virtual CheckInInfo GetCheckInInfo() const = 0;
-
- // Called when the loading from the persistent store is done. The loading
- // is triggered asynchronously when GCMClient is created.
- virtual void OnLoadingCompleted() = 0;
-
- // Returns a task runner for file operations that may block. This is used
- // in writing to or reading from the persistent store.
- virtual base::TaskRunner* GetFileTaskRunner() = 0;
- };
-
- // Returns the single instance. Multiple profiles share the same client
- // that makes use of the same MCS connection.
- static GCMClient* Get();
-
- // Passes a mocked instance for testing purpose.
- static void SetForTesting(GCMClient* client);
-
- // Checks in the user to use GCM. If the device has not been checked in, it
- // will be done first.
- // |username|: the username (email address) used to check in with the server.
- // |delegate|: the delegate whose methods will be called asynchronously in
- // response to events and messages.
- virtual void CheckIn(const std::string& username, Delegate* delegate) = 0;
-
- // Registers the application for GCM. Delegate::OnRegisterFinished will be
- // called asynchronously upon completion.
- // |username|: the username (email address) passed in CheckIn.
- // |app_id|: application ID.
- // |cert|: SHA-1 of public key of the application, in base16 format.
- // |sender_ids|: list of IDs of the servers that are allowed to send the
- // messages to the application. These IDs are assigned by the
- // Google API Console.
- virtual void Register(const std::string& username,
- const std::string& app_id,
- const std::string& cert,
- const std::vector<std::string>& sender_ids) = 0;
-
- // Unregisters the application from GCM when it is uninstalled.
- // Delegate::OnUnregisterFinished will be called asynchronously upon
- // completion.
- // |username|: the username (email address) passed in CheckIn.
- // |app_id|: application ID.
- virtual void Unregister(const std::string& username,
- const std::string& app_id) = 0;
-
- // Sends a message to a given receiver. Delegate::OnSendFinished will be
- // called asynchronously upon completion.
- // |username|: the username (email address) passed in CheckIn.
- // |app_id|: application ID.
- // |receiver_id|: registration ID of the receiver party.
- // |message|: message to be sent.
- virtual void Send(const std::string& username,
- const std::string& app_id,
- const std::string& receiver_id,
- const OutgoingMessage& message) = 0;
-
- // Returns true if the loading from the persistent store is still in progress.
- virtual bool IsLoading() const = 0;
-
- protected:
- virtual ~GCMClient() {}
-};
-
-} // namespace gcm
-
-#endif // GOOGLE_APIS_GCM_GCM_CLIENT_H_
diff --git a/chromium/google_apis/gcm/gcm_client_impl.cc b/chromium/google_apis/gcm/gcm_client_impl.cc
deleted file mode 100644
index 76900cda747..00000000000
--- a/chromium/google_apis/gcm/gcm_client_impl.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2013 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 "google_apis/gcm/gcm_client_impl.h"
-
-namespace gcm {
-
-GCMClientImpl::GCMClientImpl() {
-}
-
-GCMClientImpl::~GCMClientImpl() {
-}
-
-void GCMClientImpl::CheckIn(const std::string& username,
- Delegate* delegate) {
-}
-
-void GCMClientImpl::Register(const std::string& username,
- const std::string& app_id,
- const std::string& cert,
- const std::vector<std::string>& sender_ids) {
-}
-
-void GCMClientImpl::Unregister(const std::string& username,
- const std::string& app_id) {
-}
-
-void GCMClientImpl::Send(const std::string& username,
- const std::string& app_id,
- const std::string& receiver_id,
- const OutgoingMessage& message) {
-}
-
-bool GCMClientImpl::IsLoading() const {
- return false;
-}
-
-} // namespace gcm
diff --git a/chromium/google_apis/gcm/gcm_client_impl.h b/chromium/google_apis/gcm/gcm_client_impl.h
deleted file mode 100644
index 46f910e546a..00000000000
--- a/chromium/google_apis/gcm/gcm_client_impl.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2013 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.
-
-#ifndef GOOGLE_APIS_GCM_GCM_CLIENT_IMPL_H_
-#define GOOGLE_APIS_GCM_GCM_CLIENT_IMPL_H_
-
-#include "base/compiler_specific.h"
-#include "google_apis/gcm/gcm_client.h"
-
-namespace gcm {
-
-class GCMClientImpl : public GCMClient {
- public:
- GCMClientImpl();
- virtual ~GCMClientImpl();
-
- // Overridden from GCMClient:
- virtual void CheckIn(const std::string& username,
- Delegate* delegate) OVERRIDE;
- virtual void Register(const std::string& username,
- const std::string& app_id,
- const std::string& cert,
- const std::vector<std::string>& sender_ids) OVERRIDE;
- virtual void Unregister(const std::string& username,
- const std::string& app_id) OVERRIDE;
- virtual void Send(const std::string& username,
- const std::string& app_id,
- const std::string& receiver_id,
- const OutgoingMessage& message) OVERRIDE;
- virtual bool IsLoading() const OVERRIDE;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(GCMClientImpl);
-};
-
-} // namespace gcm
-
-#endif // GOOGLE_APIS_GCM_GCM_CLIENT_IMPL_H_
diff --git a/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc b/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc
new file mode 100644
index 00000000000..5ed8a1df49c
--- /dev/null
+++ b/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 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 "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
+
+namespace gcm {
+
+FakeGCMStatsRecorder::FakeGCMStatsRecorder() {
+}
+
+FakeGCMStatsRecorder::~FakeGCMStatsRecorder() {
+}
+
+void FakeGCMStatsRecorder::RecordCheckinInitiated(uint64 android_id) {
+}
+
+void FakeGCMStatsRecorder::RecordCheckinDelayedDueToBackoff(int64 delay_msec) {
+}
+
+void FakeGCMStatsRecorder::RecordCheckinSuccess() {
+}
+
+void FakeGCMStatsRecorder::RecordCheckinFailure(std::string status,
+ bool will_retry) {
+}
+
+void FakeGCMStatsRecorder::RecordConnectionInitiated(const std::string& host) {
+}
+
+void FakeGCMStatsRecorder::RecordConnectionDelayedDueToBackoff(
+ int64 delay_msec) {
+}
+
+void FakeGCMStatsRecorder::RecordConnectionSuccess() {
+}
+
+void FakeGCMStatsRecorder::RecordConnectionFailure(int network_error) {
+}
+
+void FakeGCMStatsRecorder::RecordConnectionResetSignaled(
+ ConnectionFactory::ConnectionResetReason reason) {
+}
+
+void FakeGCMStatsRecorder::RecordRegistrationSent(
+ const std::string& app_id,
+ const std::string& sender_ids) {
+}
+
+void FakeGCMStatsRecorder::RecordRegistrationResponse(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ RegistrationRequest::Status status) {
+}
+
+void FakeGCMStatsRecorder::RecordRegistrationRetryRequested(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ int retries_left) {
+}
+
+void FakeGCMStatsRecorder::RecordUnregistrationSent(
+ const std::string& app_id) {
+}
+
+void FakeGCMStatsRecorder::RecordUnregistrationResponse(
+ const std::string& app_id,
+ UnregistrationRequest::Status status) {
+}
+
+void FakeGCMStatsRecorder::RecordUnregistrationRetryDelayed(
+ const std::string& app_id,
+ int64 delay_msec) {
+}
+
+void FakeGCMStatsRecorder::RecordDataMessageReceived(
+ const std::string& app_id,
+ const std::string& from,
+ int message_byte_size,
+ bool to_registered_app,
+ ReceivedMessageType message_type) {
+}
+
+void FakeGCMStatsRecorder::RecordDataSentToWire(
+ const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ int queued) {
+}
+
+void FakeGCMStatsRecorder::RecordNotifySendStatus(
+ const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ gcm::MCSClient::MessageSendStatus status,
+ int byte_size,
+ int ttl) {
+}
+
+void FakeGCMStatsRecorder::RecordIncomingSendError(
+ const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id) {
+}
+
+
+} // namespace gcm
diff --git a/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h b/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h
new file mode 100644
index 00000000000..1ada61107c3
--- /dev/null
+++ b/chromium/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h
@@ -0,0 +1,71 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_MONITORING_FAKE_GCM_STATS_RECODER_H_
+#define GOOGLE_APIS_GCM_MONITORING_FAKE_GCM_STATS_RECODER_H_
+
+#include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
+
+namespace gcm {
+
+// The fake version of GCMStatsRecorder that does nothing.
+class FakeGCMStatsRecorder : public GCMStatsRecorder {
+ public:
+ FakeGCMStatsRecorder();
+ virtual ~FakeGCMStatsRecorder();
+
+ virtual void RecordCheckinInitiated(uint64 android_id) OVERRIDE;
+ virtual void RecordCheckinDelayedDueToBackoff(int64 delay_msec) OVERRIDE;
+ virtual void RecordCheckinSuccess() OVERRIDE;
+ virtual void RecordCheckinFailure(std::string status,
+ bool will_retry) OVERRIDE;
+ virtual void RecordConnectionInitiated(const std::string& host) OVERRIDE;
+ virtual void RecordConnectionDelayedDueToBackoff(int64 delay_msec) OVERRIDE;
+ virtual void RecordConnectionSuccess() OVERRIDE;
+ virtual void RecordConnectionFailure(int network_error) OVERRIDE;
+ virtual void RecordConnectionResetSignaled(
+ ConnectionFactory::ConnectionResetReason reason) OVERRIDE;
+ virtual void RecordRegistrationSent(const std::string& app_id,
+ const std::string& sender_ids) OVERRIDE;
+ virtual void RecordRegistrationResponse(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ RegistrationRequest::Status status) OVERRIDE;
+ virtual void RecordRegistrationRetryRequested(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ int retries_left) OVERRIDE;
+ virtual void RecordUnregistrationSent(const std::string& app_id) OVERRIDE;
+ virtual void RecordUnregistrationResponse(
+ const std::string& app_id,
+ UnregistrationRequest::Status status) OVERRIDE;
+ virtual void RecordUnregistrationRetryDelayed(const std::string& app_id,
+ int64 delay_msec) OVERRIDE;
+ virtual void RecordDataMessageReceived(
+ const std::string& app_id,
+ const std::string& from,
+ int message_byte_size,
+ bool to_registered_app,
+ ReceivedMessageType message_type) OVERRIDE;
+ virtual void RecordDataSentToWire(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ int queued) OVERRIDE;
+ virtual void RecordNotifySendStatus(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ MCSClient::MessageSendStatus status,
+ int byte_size,
+ int ttl) OVERRIDE;
+ virtual void RecordIncomingSendError(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeGCMStatsRecorder);
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_MONITORING_FAKE_GCM_STATS_RECODER_H_
diff --git a/chromium/google_apis/gcm/monitoring/gcm_stats_recorder.h b/chromium/google_apis/gcm/monitoring/gcm_stats_recorder.h
new file mode 100644
index 00000000000..71915693fda
--- /dev/null
+++ b/chromium/google_apis/gcm/monitoring/gcm_stats_recorder.h
@@ -0,0 +1,135 @@
+// Copyright 2014 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.
+
+#ifndef GOOGLE_APIS_GCM_MONITORING_GCM_STATS_RECORDER_H_
+#define GOOGLE_APIS_GCM_MONITORING_GCM_STATS_RECORDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time/time.h"
+#include "google_apis/gcm/base/gcm_export.h"
+#include "google_apis/gcm/engine/connection_factory.h"
+#include "google_apis/gcm/engine/mcs_client.h"
+#include "google_apis/gcm/engine/registration_request.h"
+#include "google_apis/gcm/engine/unregistration_request.h"
+
+namespace gcm {
+
+// Defines the interface to record GCM internal stats and activities for
+// debugging purpose.
+class GCM_EXPORT GCMStatsRecorder {
+ public:
+ // Type of a received message
+ enum ReceivedMessageType {
+ // Data message.
+ DATA_MESSAGE,
+ // Message that indicates some messages have been deleted on the server.
+ DELETED_MESSAGES,
+ };
+
+ // A delegate interface that allows the GCMStatsRecorderImpl instance to
+ // interact with its container.
+ class Delegate {
+ public:
+ // Called when the GCMStatsRecorderImpl is recording activities and a new
+ // activity has just been recorded.
+ virtual void OnActivityRecorded() = 0;
+ };
+
+ GCMStatsRecorder() {}
+ virtual ~GCMStatsRecorder() {}
+
+ // Records that a check-in has been initiated.
+ virtual void RecordCheckinInitiated(uint64 android_id) = 0;
+
+ // Records that a check-in has been delayed due to backoff.
+ virtual void RecordCheckinDelayedDueToBackoff(int64 delay_msec) = 0;
+
+ // Records that a check-in request has succeeded.
+ virtual void RecordCheckinSuccess() = 0;
+
+ // Records that a check-in request has failed. If a retry will be tempted then
+ // will_retry should be true.
+ virtual void RecordCheckinFailure(std::string status, bool will_retry) = 0;
+
+ // Records that a connection to MCS has been initiated.
+ virtual void RecordConnectionInitiated(const std::string& host) = 0;
+
+ // Records that a connection has been delayed due to backoff.
+ virtual void RecordConnectionDelayedDueToBackoff(int64 delay_msec) = 0;
+
+ // Records that connection has been successfully established.
+ virtual void RecordConnectionSuccess() = 0;
+
+ // Records that connection has failed with a network error code.
+ virtual void RecordConnectionFailure(int network_error) = 0;
+
+ // Records that connection reset has been signaled.
+ virtual void RecordConnectionResetSignaled(
+ ConnectionFactory::ConnectionResetReason reason) = 0;
+
+ // Records that a registration request has been sent. This could be initiated
+ // directly from API, or from retry logic.
+ virtual void RecordRegistrationSent(const std::string& app_id,
+ const std::string& sender_ids) = 0;
+
+ // Records that a registration response has been received from server.
+ virtual void RecordRegistrationResponse(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ RegistrationRequest::Status status) = 0;
+
+ // Records that a registration retry has been requested. The actual retry
+ // action may not occur until some time later according to backoff logic.
+ virtual void RecordRegistrationRetryRequested(
+ const std::string& app_id,
+ const std::vector<std::string>& sender_ids,
+ int retries_left) = 0;
+
+ // Records that an unregistration request has been sent. This could be
+ // initiated directly from API, or from retry logic.
+ virtual void RecordUnregistrationSent(const std::string& app_id) = 0;
+
+ // Records that an unregistration response has been received from server.
+ virtual void RecordUnregistrationResponse(
+ const std::string& app_id,
+ UnregistrationRequest::Status status) = 0;
+
+ // Records that an unregistration retry has been requested and delayed due to
+ // backoff logic.
+ virtual void RecordUnregistrationRetryDelayed(const std::string& app_id,
+ int64 delay_msec) = 0;
+
+ // Records that a data message has been received. If this message is not
+ // sent to a registered app, to_registered_app shoudl be false. If it
+ // indicates that a message has been dropped on the server, is_message_dropped
+ // should be true.
+ virtual void RecordDataMessageReceived(const std::string& app_id,
+ const std::string& from,
+ int message_byte_size,
+ bool to_registered_app,
+ ReceivedMessageType message_type) = 0;
+
+ // Records that an outgoing data message was sent over the wire.
+ virtual void RecordDataSentToWire(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ int queued) = 0;
+ // Records that the MCS client sent a 'send status' notification to callback.
+ virtual void RecordNotifySendStatus(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id,
+ MCSClient::MessageSendStatus status,
+ int byte_size,
+ int ttl) = 0;
+ // Records that a 'send error' message was received.
+ virtual void RecordIncomingSendError(const std::string& app_id,
+ const std::string& receiver_id,
+ const std::string& message_id) = 0;
+};
+
+} // namespace gcm
+
+#endif // GOOGLE_APIS_GCM_MONITORING_GCM_STATS_RECORDER_H_
diff --git a/chromium/google_apis/gcm/protocol/android_checkin.proto b/chromium/google_apis/gcm/protocol/android_checkin.proto
new file mode 100644
index 00000000000..a4520513888
--- /dev/null
+++ b/chromium/google_apis/gcm/protocol/android_checkin.proto
@@ -0,0 +1,97 @@
+// Copyright 2014 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.
+//
+// Logging information for Android "checkin" events (automatic, periodic
+// requests made by Android devices to the server).
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option retain_unknown_fields = true;
+package checkin_proto;
+
+// Build characteristics unique to the Chrome browser, and Chrome OS
+message ChromeBuildProto {
+ enum Platform {
+ PLATFORM_WIN = 1;
+ PLATFORM_MAC = 2;
+ PLATFORM_LINUX = 3;
+ PLATFORM_CROS = 4;
+ PLATFORM_IOS = 5;
+ // Just a placeholder. Likely don't need it due to the presence of the
+ // Android GCM on phone/tablet devices.
+ PLATFORM_ANDROID = 6;
+ }
+
+ enum Channel {
+ CHANNEL_STABLE = 1;
+ CHANNEL_BETA = 2;
+ CHANNEL_DEV = 3;
+ CHANNEL_CANARY = 4;
+ CHANNEL_UNKNOWN = 5; // for tip of tree or custom builds
+ }
+
+ // The platform of the device.
+ optional Platform platform = 1;
+
+ // The Chrome instance's version.
+ optional string chrome_version = 2;
+
+ // The Channel (build type) of Chrome.
+ optional Channel channel = 3;
+}
+
+// Information sent by the device in a "checkin" request.
+message AndroidCheckinProto {
+ // Miliseconds since the Unix epoch of the device's last successful checkin.
+ optional int64 last_checkin_msec = 2;
+
+ // The current MCC+MNC of the mobile device's current cell.
+ optional string cell_operator = 6;
+
+ // The MCC+MNC of the SIM card (different from operator if the
+ // device is roaming, for instance).
+ optional string sim_operator = 7;
+
+ // The device's current roaming state (reported starting in eclair builds).
+ // Currently one of "{,not}mobile-{,not}roaming", if it is present at all.
+ optional string roaming = 8;
+
+ // For devices supporting multiple user profiles (which may be
+ // supported starting in jellybean), the ordinal number of the
+ // profile that is checking in. This is 0 for the primary profile
+ // (which can't be changed without wiping the device), and 1,2,3,...
+ // for additional profiles (which can be added and deleted freely).
+ optional int32 user_number = 9;
+
+ // Class of device. Indicates the type of build proto
+ // (IosBuildProto/ChromeBuildProto/AndroidBuildProto)
+ // That is included in this proto
+ optional DeviceType type = 12 [default = DEVICE_ANDROID_OS];
+
+ // For devices running MCS on Chrome, build-specific characteristics
+ // of the browser. There are no hardware aspects (except for ChromeOS).
+ // This will only be populated for Chrome builds/ChromeOS devices
+ optional checkin_proto.ChromeBuildProto chrome_build = 13;
+
+ // Note: Some of the Android specific optional fields were skipped to limit
+ // the protobuf definition.
+ // Next 14
+}
+
+// enum values correspond to the type of device.
+// Used in the AndroidCheckinProto and Device proto.
+enum DeviceType {
+ // Android Device
+ DEVICE_ANDROID_OS = 1;
+
+ // Apple IOS device
+ DEVICE_IOS_OS = 2;
+
+ // Chrome browser - Not Chrome OS. No hardware records.
+ DEVICE_CHROME_BROWSER = 3;
+
+ // Chrome OS
+ DEVICE_CHROME_OS = 4;
+}
diff --git a/chromium/google_apis/gcm/protocol/checkin.proto b/chromium/google_apis/gcm/protocol/checkin.proto
new file mode 100644
index 00000000000..10ad628f733
--- /dev/null
+++ b/chromium/google_apis/gcm/protocol/checkin.proto
@@ -0,0 +1,156 @@
+// Copyright 2014 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.
+//
+// Request and reply to the "checkin server" devices poll every few hours.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option retain_unknown_fields = true;
+
+package checkin_proto;
+
+import "android_checkin.proto";
+
+// A concrete name/value pair sent to the device's Gservices database.
+message GservicesSetting {
+ required bytes name = 1;
+ required bytes value = 2;
+}
+
+// Devices send this every few hours to tell us how they're doing.
+message AndroidCheckinRequest {
+ // IMEI (used by GSM phones) is sent and stored as 15 decimal
+ // digits; the 15th is a check digit.
+ optional string imei = 1; // IMEI, reported but not logged.
+
+ // MEID (used by CDMA phones) is sent and stored as 14 hexadecimal
+ // digits (no check digit).
+ optional string meid = 10; // MEID, reported but not logged.
+
+ // MAC address (used by non-phone devices). 12 hexadecimal digits;
+ // no separators (eg "0016E6513AC2", not "00:16:E6:51:3A:C2").
+ repeated string mac_addr = 9; // MAC address, reported but not logged.
+
+ // An array parallel to mac_addr, describing the type of interface.
+ // Currently accepted values: "wifi", "ethernet", "bluetooth". If
+ // not present, "wifi" is assumed.
+ repeated string mac_addr_type = 19;
+
+ // Serial number (a manufacturer-defined unique hardware
+ // identifier). Alphanumeric, case-insensitive.
+ optional string serial_number = 16;
+
+ // Older CDMA networks use an ESN (8 hex digits) instead of an MEID.
+ optional string esn = 17; // ESN, reported but not logged
+
+ optional int64 id = 2; // Android device ID, not logged
+ optional int64 logging_id = 7; // Pseudonymous logging ID for Sawmill
+ optional string digest = 3; // Digest of device provisioning, not logged.
+ optional string locale = 6; // Current locale in standard (xx_XX) format
+ required AndroidCheckinProto checkin = 4;
+
+ // DEPRECATED, see AndroidCheckinProto.requested_group
+ optional string desired_build = 5;
+
+ // Blob of data from the Market app to be passed to Market API server
+ optional string market_checkin = 8;
+
+ // SID cookies of any google accounts stored on the phone. Not logged.
+ repeated string account_cookie = 11;
+
+ // Time zone. Not currently logged.
+ optional string time_zone = 12;
+
+ // Security token used to validate the checkin request.
+ // Required for android IDs issued to Froyo+ devices, not for legacy IDs.
+ optional fixed64 security_token = 13;
+
+ // Version of checkin protocol.
+ //
+ // There are currently two versions:
+ //
+ // - version field missing: android IDs are assigned based on
+ // hardware identifiers. unsecured in the sense that you can
+ // "unregister" someone's phone by sending a registration request
+ // with their IMEI/MEID/MAC.
+ //
+ // - version=2: android IDs are assigned randomly. The device is
+ // sent a security token that must be included in all future
+ // checkins for that android id.
+ //
+ // - version=3: same as version 2, but the 'fragment' field is
+ // provided, and the device understands incremental updates to the
+ // gservices table (ie, only returning the keys whose values have
+ // changed.)
+ //
+ // (version=1 was skipped to avoid confusion with the "missing"
+ // version field that is effectively version 1.)
+ optional int32 version = 14;
+
+ // OTA certs accepted by device (base-64 SHA-1 of cert files). Not
+ // logged.
+ repeated string ota_cert = 15;
+
+ // Honeycomb and newer devices send configuration data with their checkin.
+ // optional DeviceConfigurationProto device_configuration = 18;
+
+ // A single CheckinTask on the device may lead to multiple checkin
+ // requests if there is too much log data to upload in a single
+ // request. For version 3 and up, this field will be filled in with
+ // the number of the request, starting with 0.
+ optional int32 fragment = 20;
+
+ // For devices supporting multiple users, the name of the current
+ // profile (they all check in independently, just as if they were
+ // multiple physical devices). This may not be set, even if the
+ // device is using multiuser. (checkin.user_number should be set to
+ // the ordinal of the user.)
+ optional string user_name = 21;
+
+ // For devices supporting multiple user profiles, the serial number
+ // for the user checking in. Not logged. May not be set, even if
+ // the device supportes multiuser. checkin.user_number is the
+ // ordinal of the user (0, 1, 2, ...), which may be reused if users
+ // are deleted and re-created. user_serial_number is never reused
+ // (unless the device is wiped).
+ optional int32 user_serial_number = 22;
+
+ // NEXT TAG: 23
+}
+
+// The response to the device.
+message AndroidCheckinResponse {
+ required bool stats_ok = 1; // Whether statistics were recorded properly.
+ optional int64 time_msec = 3; // Time of day from server (Java epoch).
+ // repeated AndroidIntentProto intent = 2;
+
+ // Provisioning is sent if the request included an obsolete digest.
+ //
+ // For version <= 2, 'digest' contains the digest that should be
+ // sent back to the server on the next checkin, and 'setting'
+ // contains the entire gservices table (which replaces the entire
+ // current table on the device).
+ //
+ // for version >= 3, 'digest' will be absent. If 'settings_diff'
+ // is false, then 'setting' contains the entire table, as in version
+ // 2. If 'settings_diff' is true, then 'delete_setting' contains
+ // the keys to delete, and 'setting' contains only keys to be added
+ // or for which the value has changed. All other keys in the
+ // current table should be left untouched. If 'settings_diff' is
+ // absent, don't touch the existing gservices table.
+ //
+ optional string digest = 4;
+ optional bool settings_diff = 9;
+ repeated string delete_setting = 10;
+ repeated GservicesSetting setting = 5;
+
+ optional bool market_ok = 6; // If Market got the market_checkin data OK.
+
+ optional fixed64 android_id = 7; // From the request, or newly assigned
+ optional fixed64 security_token = 8; // The associated security token
+
+ optional string version_info = 11;
+ // NEXT TAG: 12
+}
diff --git a/chromium/google_apis/gcm/tools/mcs_probe.cc b/chromium/google_apis/gcm/tools/mcs_probe.cc
index bc4ad7cad3a..1bb0bc14dfc 100644
--- a/chromium/google_apis/gcm/tools/mcs_probe.cc
+++ b/chromium/google_apis/gcm/tools/mcs_probe.cc
@@ -8,6 +8,7 @@
#include <cstddef>
#include <cstdio>
#include <string>
+#include <vector>
#include "base/at_exit.h"
#include "base/command_line.h"
@@ -20,11 +21,17 @@
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread.h"
#include "base/threading/worker_pool.h"
+#include "base/time/default_clock.h"
#include "base/values.h"
+#include "google_apis/gcm/base/fake_encryptor.h"
#include "google_apis/gcm/base/mcs_message.h"
#include "google_apis/gcm/base/mcs_util.h"
+#include "google_apis/gcm/engine/checkin_request.h"
#include "google_apis/gcm/engine/connection_factory_impl.h"
+#include "google_apis/gcm/engine/gcm_store_impl.h"
+#include "google_apis/gcm/engine/gservices_settings.h"
#include "google_apis/gcm/engine/mcs_client.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
#include "net/base/host_mapping_rules.h"
#include "net/base/net_log_logger.h"
#include "net/cert/cert_verifier.h"
@@ -48,6 +55,35 @@
namespace gcm {
namespace {
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ 0,
+
+ // Initial delay for exponential back-off in ms.
+ 15000, // 15 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.5, // 50%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 60 * 5, // 5 minutes.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+// Default values used to communicate with the check-in server.
+const char kChromeVersion[] = "Chrome MCS Probe";
+
// The default server to communicate with.
const char kMCSServerHost[] = "mtalk.google.com";
const uint16 kMCSServerPort = 5228;
@@ -82,8 +118,14 @@ void MessageReceivedCallback(const MCSMessage& message) {
}
}
-void MessageSentCallback(const std::string& local_id) {
- LOG(INFO) << "Message sent. Status: " << local_id;
+void MessageSentCallback(int64 user_serial_number,
+ const std::string& app_id,
+ const std::string& message_id,
+ MCSClient::MessageSendStatus status) {
+ LOG(INFO) << "Message sent. Serial number: " << user_serial_number
+ << " Application ID: " << app_id
+ << " Message ID: " << message_id
+ << " Message send status: " << status;
}
// Needed to use a real host resolver.
@@ -164,16 +206,22 @@ class MCSProbe {
uint64 secret() const { return secret_; }
private:
+ void CheckIn();
void InitializeNetworkState();
void BuildNetworkSession();
- void InitializationCallback(bool success,
- uint64 restored_android_id,
- uint64 restored_security_token);
+ void LoadCallback(scoped_ptr<GCMStore::LoadResult> load_result);
+ void UpdateCallback(bool success);
+ void ErrorCallback();
+ void OnCheckInCompleted(
+ const checkin_proto::AndroidCheckinResponse& checkin_response);
+ void StartMCSLogin();
+
+ base::DefaultClock clock_;
CommandLine command_line_;
- base::FilePath rmq_path_;
+ base::FilePath gcm_store_path_;
uint64 android_id_;
uint64 secret_;
std::string server_host_;
@@ -195,7 +243,10 @@ class MCSProbe {
scoped_refptr<net::HttpNetworkSession> network_session_;
scoped_ptr<net::ProxyService> proxy_service_;
+ FakeGCMStatsRecorder recorder_;
+ scoped_ptr<GCMStore> gcm_store_;
scoped_ptr<MCSClient> mcs_client_;
+ scoped_ptr<CheckinRequest> checkin_request_;
scoped_ptr<ConnectionFactoryImpl> connection_factory_;
@@ -208,14 +259,14 @@ MCSProbe::MCSProbe(
const CommandLine& command_line,
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter)
: command_line_(command_line),
- rmq_path_(base::FilePath(FILE_PATH_LITERAL("gcm_rmq_store"))),
+ gcm_store_path_(base::FilePath(FILE_PATH_LITERAL("gcm_store"))),
android_id_(0),
secret_(0),
server_port_(0),
url_request_context_getter_(url_request_context_getter),
file_thread_("FileThread") {
if (command_line.HasSwitch(kRMQFileName)) {
- rmq_path_ = command_line.GetSwitchValuePath(kRMQFileName);
+ gcm_store_path_ = command_line.GetSwitchValuePath(kRMQFileName);
}
if (command_line.HasSwitch(kAndroidIdSwitch)) {
base::StringToUint64(command_line.GetSwitchValueASCII(kAndroidIdSwitch),
@@ -244,22 +295,64 @@ void MCSProbe::Start() {
file_thread_.Start();
InitializeNetworkState();
BuildNetworkSession();
+ std::vector<GURL> endpoints(1,
+ GURL("https://" +
+ net::HostPortPair(server_host_,
+ server_port_).ToString()));
connection_factory_.reset(
- new ConnectionFactoryImpl(GURL("https://" + net::HostPortPair(
- server_host_, server_port_).ToString()),
+ new ConnectionFactoryImpl(endpoints,
+ kDefaultBackoffPolicy,
network_session_,
- &net_log_));
- mcs_client_.reset(new MCSClient(rmq_path_,
+ &net_log_,
+ &recorder_));
+ gcm_store_.reset(
+ new GCMStoreImpl(gcm_store_path_,
+ file_thread_.message_loop_proxy(),
+ make_scoped_ptr<Encryptor>(new FakeEncryptor)));
+ mcs_client_.reset(new MCSClient("probe",
+ &clock_,
connection_factory_.get(),
- file_thread_.message_loop_proxy()));
+ gcm_store_.get(),
+ &recorder_));
run_loop_.reset(new base::RunLoop());
- mcs_client_->Initialize(base::Bind(&MCSProbe::InitializationCallback,
- base::Unretained(this)),
- base::Bind(&MessageReceivedCallback),
- base::Bind(&MessageSentCallback));
+ gcm_store_->Load(base::Bind(&MCSProbe::LoadCallback,
+ base::Unretained(this)));
run_loop_->Run();
}
+void MCSProbe::LoadCallback(scoped_ptr<GCMStore::LoadResult> load_result) {
+ DCHECK(load_result->success);
+ if (android_id_ != 0 && secret_ != 0) {
+ DVLOG(1) << "Presetting MCS id " << android_id_;
+ load_result->device_android_id = android_id_;
+ load_result->device_security_token = secret_;
+ gcm_store_->SetDeviceCredentials(android_id_,
+ secret_,
+ base::Bind(&MCSProbe::UpdateCallback,
+ base::Unretained(this)));
+ } else {
+ android_id_ = load_result->device_android_id;
+ secret_ = load_result->device_security_token;
+ DVLOG(1) << "Loaded MCS id " << android_id_;
+ }
+ mcs_client_->Initialize(
+ base::Bind(&MCSProbe::ErrorCallback, base::Unretained(this)),
+ base::Bind(&MessageReceivedCallback),
+ base::Bind(&MessageSentCallback),
+ load_result.Pass());
+
+ if (!android_id_ || !secret_) {
+ DVLOG(1) << "Checkin to generate new MCS credentials.";
+ CheckIn();
+ return;
+ }
+
+ StartMCSLogin();
+}
+
+void MCSProbe::UpdateCallback(bool success) {
+}
+
void MCSProbe::InitializeNetworkState() {
FILE* log_file = NULL;
if (command_line_.HasSwitch(kLogFileSwitch)) {
@@ -317,7 +410,6 @@ void MCSProbe::BuildNetworkSession() {
session_params.network_delegate = NULL; // TODO(zea): implement?
session_params.host_mapping_rules = host_mapping_rules_.get();
session_params.ignore_certificate_errors = true;
- session_params.http_pipelining_enabled = false;
session_params.testing_fixed_http_port = 0;
session_params.testing_fixed_https_port = 0;
session_params.net_log = &net_log_;
@@ -326,16 +418,59 @@ void MCSProbe::BuildNetworkSession() {
network_session_ = new net::HttpNetworkSession(session_params);
}
-void MCSProbe::InitializationCallback(bool success,
- uint64 restored_android_id,
- uint64 restored_security_token) {
- LOG(INFO) << "Initialization " << (success ? "success!" : "failure!");
- if (restored_android_id && restored_security_token) {
- android_id_ = restored_android_id;
- secret_ = restored_security_token;
- }
- if (success)
- mcs_client_->Login(android_id_, secret_);
+void MCSProbe::ErrorCallback() {
+ LOG(INFO) << "MCS error happened";
+}
+
+void MCSProbe::CheckIn() {
+ LOG(INFO) << "Check-in request initiated.";
+ checkin_proto::ChromeBuildProto chrome_build_proto;
+ chrome_build_proto.set_platform(
+ checkin_proto::ChromeBuildProto::PLATFORM_LINUX);
+ chrome_build_proto.set_channel(
+ checkin_proto::ChromeBuildProto::CHANNEL_CANARY);
+ chrome_build_proto.set_chrome_version(kChromeVersion);
+
+ CheckinRequest::RequestInfo request_info(
+ 0, 0, std::string(), chrome_build_proto);
+
+ checkin_request_.reset(new CheckinRequest(
+ GServicesSettings::DefaultCheckinURL(),
+ request_info,
+ kDefaultBackoffPolicy,
+ base::Bind(&MCSProbe::OnCheckInCompleted, base::Unretained(this)),
+ url_request_context_getter_.get(),
+ &recorder_));
+ checkin_request_->Start();
+}
+
+void MCSProbe::OnCheckInCompleted(
+ const checkin_proto::AndroidCheckinResponse& checkin_response) {
+ bool success = checkin_response.has_android_id() &&
+ checkin_response.android_id() != 0UL &&
+ checkin_response.has_security_token() &&
+ checkin_response.security_token() != 0UL;
+ LOG(INFO) << "Check-in request completion "
+ << (success ? "success!" : "failure!");
+
+ if (!success)
+ return;
+
+ android_id_ = checkin_response.android_id();
+ secret_ = checkin_response.security_token();
+
+ gcm_store_->SetDeviceCredentials(android_id_,
+ secret_,
+ base::Bind(&MCSProbe::UpdateCallback,
+ base::Unretained(this)));
+
+ StartMCSLogin();
+}
+
+void MCSProbe::StartMCSLogin() {
+ LOG(INFO) << "MCS login initiated.";
+
+ mcs_client_->Login(android_id_, secret_);
}
int MCSProbeMain(int argc, char* argv[]) {
diff --git a/chromium/google_apis/google_api_keys.cc b/chromium/google_apis/google_api_keys.cc
index 94f6812ab74..f879979bc78 100644
--- a/chromium/google_apis/google_api_keys.cc
+++ b/chromium/google_apis/google_api_keys.cc
@@ -12,6 +12,7 @@
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/stringize_macros.h"
+#include "google_apis/gaia/gaia_switches.h"
#if defined(GOOGLE_CHROME_BUILD) || defined(USE_OFFICIAL_GOOGLE_API_KEYS)
#include "google_apis/internal/google_chrome_api_keys.h"
@@ -69,16 +70,6 @@
#define GOOGLE_DEFAULT_CLIENT_SECRET ""
#endif
-namespace switches {
-
-// Specifies custom OAuth2 client id for testing purposes.
-const char kOAuth2ClientID[] = "oauth2-client-id";
-
-// Specifies custom OAuth2 client secret for testing purposes.
-const char kOAuth2ClientSecret[] = "oauth2-client-secret";
-
-} // namespace switches
-
namespace google_apis {
// This is used as a lazy instance to determine keys once and cache them.
@@ -269,4 +260,12 @@ std::string GetOAuth2ClientSecret(OAuth2Client client) {
return g_api_key_cache.Get().GetClientSecret(client);
}
+bool IsGoogleChromeAPIKeyUsed() {
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_OFFICIAL_GOOGLE_API_KEYS)
+ return true;
+#else
+ return false;
+#endif
+}
+
} // namespace google_apis
diff --git a/chromium/google_apis/google_api_keys.h b/chromium/google_apis/google_api_keys.h
index 8f4bc7e9cac..5acdf263a6f 100644
--- a/chromium/google_apis/google_api_keys.h
+++ b/chromium/google_apis/google_api_keys.h
@@ -90,6 +90,10 @@ std::string GetOAuth2ClientID(OAuth2Client client);
// in, e.g. URL-escaped if you use it in a URL.
std::string GetOAuth2ClientSecret(OAuth2Client client);
+// Returns if the API key using in the current build is the one for official
+// Google Chrome.
+bool IsGoogleChromeAPIKeyUsed();
+
} // namespace google_apis
#endif // GOOGLE_APIS_GOOGLE_API_KEYS_H_
diff --git a/chromium/google_apis/google_api_keys_unittest.cc b/chromium/google_apis/google_api_keys_unittest.cc
index 7432f93e830..e18cff52f18 100644
--- a/chromium/google_apis/google_api_keys_unittest.cc
+++ b/chromium/google_apis/google_api_keys_unittest.cc
@@ -13,6 +13,7 @@
#include "google_apis/google_api_keys.h"
#include "build/build_config.h"
+#include "google_apis/gaia/gaia_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
// The Win builders fail (with a linker crash) when trying to link
diff --git a/chromium/google_apis/google_apis.gyp b/chromium/google_apis/google_apis.gyp
index 2d281f2b2f3..77dfce9c5d6 100644
--- a/chromium/google_apis/google_apis.gyp
+++ b/chromium/google_apis/google_apis.gyp
@@ -36,23 +36,29 @@
'GOOGLE_DEFAULT_CLIENT_SECRET="<(google_default_client_secret)"',
]
}],
- [ 'OS == "android"', {
- 'dependencies': [
- '../third_party/openssl/openssl.gyp:openssl',
- ],
- 'sources/': [
- ['exclude', 'cup/client_update_protocol_nss\.cc$'],
- ],
+ ['OS == "mac" or OS == "ios" or OS == "win"', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nspr',
+ '../third_party/nss/nss.gyp:nss',
+ ],
+ }],
+ ['OS == "android"', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ ],
+ 'sources/': [
+ ['exclude', 'cup/client_update_protocol_nss\.cc$'],
+ ],
+ }],
+ ['use_openssl==1', {
+ 'sources!': [
+ 'cup/client_update_protocol_nss.cc',
+ ],
+ }, {
+ 'sources!': [
+ 'cup/client_update_protocol_openssl.cc',
+ ],
}],
- [ 'use_openssl==1', {
- 'sources!': [
- 'cup/client_update_protocol_nss.cc',
- ],
- }, {
- 'sources!': [
- 'cup/client_update_protocol_openssl.cc',
- ],
- },],
],
'sources': [
'cup/client_update_protocol.cc',
@@ -73,8 +79,6 @@
'drive/drive_api_url_generator.h',
'drive/drive_common_callbacks.h',
'drive/drive_entry_kinds.h',
- 'drive/gdata_contacts_requests.cc',
- 'drive/gdata_contacts_requests.h',
'drive/gdata_errorcode.cc',
'drive/gdata_errorcode.h',
'drive/gdata_wapi_requests.cc',
@@ -91,6 +95,8 @@
'drive/task_util.h',
'drive/time_util.cc',
'drive/time_util.h',
+ 'gaia/account_tracker.cc',
+ 'gaia/account_tracker.h',
'gaia/gaia_auth_consumer.cc',
'gaia/gaia_auth_consumer.h',
'gaia/gaia_auth_fetcher.cc',
@@ -107,17 +113,27 @@
'gaia/gaia_urls.h',
'gaia/google_service_auth_error.cc',
'gaia/google_service_auth_error.h',
+ 'gaia/identity_provider.cc',
+ 'gaia/identity_provider.h',
+ 'gaia/merge_session_helper.cc',
+ 'gaia/merge_session_helper.h',
'gaia/oauth_request_signer.cc',
'gaia/oauth_request_signer.h',
'gaia/oauth2_access_token_consumer.h',
- 'gaia/oauth2_access_token_fetcher.cc',
'gaia/oauth2_access_token_fetcher.h',
+ 'gaia/oauth2_access_token_fetcher.cc',
+ 'gaia/oauth2_access_token_fetcher_impl.cc',
+ 'gaia/oauth2_access_token_fetcher_impl.h',
'gaia/oauth2_api_call_flow.cc',
'gaia/oauth2_api_call_flow.h',
'gaia/oauth2_mint_token_flow.cc',
'gaia/oauth2_mint_token_flow.h',
'gaia/oauth2_token_service.cc',
'gaia/oauth2_token_service.h',
+ 'gaia/oauth2_token_service_request.cc',
+ 'gaia/oauth2_token_service_request.h',
+ 'gaia/ubertoken_fetcher.cc',
+ 'gaia/ubertoken_fetcher.h',
'google_api_keys.cc',
'google_api_keys.h',
],
@@ -129,14 +145,55 @@
'type': 'executable',
'dependencies': [
'../base/base.gyp:run_all_unittests',
+ '../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
'google_apis',
+ 'google_apis_test_support',
+ ],
+ 'includes': [
+ 'determine_use_official_keys.gypi',
],
'include_dirs': [
'..',
],
'sources': [
'google_api_keys_unittest.cc',
+ 'cup/client_update_protocol_unittest.cc',
+ 'drive/base_requests_unittest.cc',
+ 'drive/base_requests_server_unittest.cc',
+ 'drive/drive_api_requests_unittest.cc',
+ 'drive/drive_api_parser_unittest.cc',
+ 'drive/drive_api_url_generator_unittest.cc',
+ 'drive/gdata_wapi_parser_unittest.cc',
+ 'drive/gdata_wapi_requests_unittest.cc',
+ 'drive/gdata_wapi_url_generator_unittest.cc',
+ 'drive/request_sender_unittest.cc',
+ 'drive/request_util_unittest.cc',
+ 'drive/time_util_unittest.cc',
+ 'gaia/account_tracker_unittest.cc',
+ 'gaia/gaia_auth_fetcher_unittest.cc',
+ 'gaia/gaia_auth_util_unittest.cc',
+ 'gaia/gaia_oauth_client_unittest.cc',
+ 'gaia/google_service_auth_error_unittest.cc',
+ 'gaia/merge_session_helper_unittest.cc',
+ 'gaia/oauth_request_signer_unittest.cc',
+ 'gaia/oauth2_access_token_fetcher_impl_unittest.cc',
+ 'gaia/oauth2_api_call_flow_unittest.cc',
+ 'gaia/oauth2_mint_token_flow_unittest.cc',
+ 'gaia/oauth2_token_service_request_unittest.cc',
+ 'gaia/oauth2_token_service_unittest.cc',
+ 'gaia/ubertoken_fetcher_unittest.cc',
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'sources!': [
+ 'drive/base_requests_server_unittest.cc',
+ 'drive/drive_api_parser_unittest.cc',
+ 'drive/drive_api_requests_unittest.cc',
+ 'drive/gdata_wapi_parser_unittest.cc',
+ 'drive/gdata_wapi_requests_unittest.cc',
+ ],
+ }],
],
},
{
@@ -155,8 +212,19 @@
'../net/net.gyp:net_test_support',
],
'sources': [
+ 'drive/dummy_auth_service.cc',
+ 'drive/dummy_auth_service.h',
+ 'drive/test_util.cc',
+ 'drive/test_util.h',
'gaia/fake_gaia.cc',
'gaia/fake_gaia.h',
+ 'gaia/fake_identity_provider.cc',
+ 'gaia/fake_identity_provider.h',
+ 'gaia/fake_oauth2_token_service.cc',
+ 'gaia/fake_oauth2_token_service.h',
+ 'gaia/mock_url_fetcher_factory.h',
+ 'gaia/oauth2_token_service_test_util.cc',
+ 'gaia/oauth2_token_service_test_util.h',
],
},
],