blob: 7e53c741ccb627fab21346a6f20b621696bec980 [file] [log] [blame]
jianli1235e51c2014-09-08 18:56:411// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/gcm_driver/gcm_channel_status_request.h"
6
7#include "base/bind.h"
skyostilb0daa012015-06-02 19:03:488#include "base/location.h"
9#include "base/single_thread_task_runner.h"
gab7966d312016-05-11 20:35:0110#include "base/threading/thread_task_runner_handle.h"
rajendrantbfee22e2017-01-20 20:34:1711#include "components/data_use_measurement/core/data_use_user_data.h"
jianli1235e51c2014-09-08 18:56:4112#include "components/gcm_driver/gcm_backoff_policy.h"
Max Boguefef332d2016-07-28 22:09:0913#include "components/sync/protocol/experiment_status.pb.h"
jianli1235e51c2014-09-08 18:56:4114#include "net/base/escape.h"
15#include "net/base/load_flags.h"
16#include "net/http/http_status_code.h"
rhalavati85dc67542017-02-22 13:00:1317#include "net/traffic_annotation/network_traffic_annotation.h"
Mark Pilgrim7634f5b52018-06-27 19:53:2718#include "services/network/public/cpp/resource_request.h"
19#include "services/network/public/cpp/shared_url_loader_factory.h"
20#include "services/network/public/cpp/simple_url_loader.h"
jianli1235e51c2014-09-08 18:56:4121#include "url/gurl.h"
22
23namespace gcm {
24
25namespace {
26
jianlic6338d72014-09-16 02:25:1127const char kRequestContentType[] = "application/octet-stream";
jianli1235e51c2014-09-08 18:56:4128const char kGCMChannelTag[] = "gcm_channel";
29const int kDefaultPollIntervalSeconds = 60 * 60; // 60 minutes.
30const int kMinPollIntervalSeconds = 30 * 60; // 30 minutes.
31
32} // namespace
33
34GCMChannelStatusRequest::GCMChannelStatusRequest(
Mark Pilgrim7634f5b52018-06-27 19:53:2735 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
fgorski3f6b84cd2014-10-10 21:04:1836 const std::string& channel_status_request_url,
37 const std::string& user_agent,
jianli1235e51c2014-09-08 18:56:4138 const GCMChannelStatusRequestCallback& callback)
Mark Pilgrim7634f5b52018-06-27 19:53:2739 : url_loader_factory_(url_loader_factory),
fgorski3f6b84cd2014-10-10 21:04:1840 channel_status_request_url_(channel_status_request_url),
41 user_agent_(user_agent),
jianli1235e51c2014-09-08 18:56:4142 callback_(callback),
43 backoff_entry_(&(GetGCMBackoffPolicy())),
Mark Pilgrim7634f5b52018-06-27 19:53:2744 weak_ptr_factory_(this) {}
jianli1235e51c2014-09-08 18:56:4145
46GCMChannelStatusRequest::~GCMChannelStatusRequest() {
47}
48
49// static
jianli2dc910b02014-09-19 02:42:4650int GCMChannelStatusRequest::default_poll_interval_seconds() {
jianli1235e51c2014-09-08 18:56:4151 return kDefaultPollIntervalSeconds;
52}
53
54// static
jianli2dc910b02014-09-19 02:42:4655int GCMChannelStatusRequest::min_poll_interval_seconds() {
jianli1235e51c2014-09-08 18:56:4156 return kMinPollIntervalSeconds;
57}
58
59void GCMChannelStatusRequest::Start() {
Mark Pilgrim7634f5b52018-06-27 19:53:2760 // url_loader_factory_ can be null for tests.
61 if (!url_loader_factory_)
62 return;
63
64 DCHECK(!simple_url_loader_);
jianli1235e51c2014-09-08 18:56:4165
fgorski3f6b84cd2014-10-10 21:04:1866 GURL request_url(channel_status_request_url_);
jianli1235e51c2014-09-08 18:56:4167
fgorski3689f8d2014-10-09 04:39:3168 sync_pb::ExperimentStatusRequest proto_data;
jianli1235e51c2014-09-08 18:56:4169 proto_data.add_experiment_name(kGCMChannelTag);
70 std::string upload_data;
jianli115ee922014-10-17 21:03:5771 if (!proto_data.SerializeToString(&upload_data)) {
72 NOTREACHED();
73 }
jianli1235e51c2014-09-08 18:56:4174
rhalavati85dc67542017-02-22 13:00:1375 net::NetworkTrafficAnnotationTag traffic_annotation =
76 net::DefineNetworkTrafficAnnotation("gcm_channel_status_request", R"(
77 semantics {
78 sender: "GCM Driver"
79 description:
80 "Google Chrome interacts with Google Cloud Messaging to receive "
81 "push messages for various browser features, as well as on behalf "
82 "of websites and extensions. The channel status request "
83 "periodically confirms with Google servers whether the feature "
84 "should be enabled."
85 trigger:
86 "Periodically when Chrome has established an active Google Cloud "
87 "Messaging subscription. The first request will be issued a minute "
88 "after the first subscription activates. Subsequent requests will "
89 "be issued each hour with a jitter of 15 minutes. Google can "
90 "adjust this interval when it deems necessary."
91 data:
92 "A user agent string containing the Chrome version, channel and "
93 "platform will be sent to the server. No user identifier is sent "
94 "along with the request."
95 destination: GOOGLE_OWNED_SERVICE
96 }
97 policy {
Ramin Halavati3b979782017-07-21 11:40:2698 cookies_allowed: NO
rhalavati85dc67542017-02-22 13:00:1399 setting:
100 "Support for interacting with Google Cloud Messaging is enabled by "
101 "default, and there is no configuration option to completely "
102 "disable it. Websites wishing to receive push messages must "
103 "acquire express permission from the user for the 'Notification' "
104 "permission."
105 policy_exception_justification:
106 "Not implemented, considered not useful."
107 })");
108
Mark Pilgrim7634f5b52018-06-27 19:53:27109 auto resource_request = std::make_unique<network::ResourceRequest>();
110
111 resource_request->url = request_url;
112 resource_request->load_flags =
113 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;
114 resource_request->method = "POST";
115 resource_request->headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
116 user_agent_);
117 // TODO(https://crbug.com/808498): Re-add data use measurement once
118 // SimpleURLLoader supports it.
119 // ID=data_use_measurement::DataUseUserData::GCM_DRIVER
120 simple_url_loader_ = network::SimpleURLLoader::Create(
121 std::move(resource_request), traffic_annotation);
122 simple_url_loader_->AttachStringForUpload(upload_data, kRequestContentType);
123 simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
124 url_loader_factory_.get(),
125 base::BindOnce(&GCMChannelStatusRequest::OnSimpleLoaderComplete,
126 base::Unretained(this)));
jianli1235e51c2014-09-08 18:56:41127}
128
Mark Pilgrim7634f5b52018-06-27 19:53:27129void GCMChannelStatusRequest::OnSimpleLoaderComplete(
130 std::unique_ptr<std::string> response_body) {
131 if (ParseResponse(std::move(response_body)))
jianli1235e51c2014-09-08 18:56:41132 return;
133
134 RetryWithBackoff(true);
135}
136
Mark Pilgrim7634f5b52018-06-27 19:53:27137bool GCMChannelStatusRequest::ParseResponse(
138 std::unique_ptr<std::string> response_body) {
139 if (simple_url_loader_->ResponseInfo() &&
140 simple_url_loader_->ResponseInfo()->headers) {
141 int response_code =
142 simple_url_loader_->ResponseInfo()->headers->response_code();
143 if (response_code != net::HTTP_OK) {
144 LOG(ERROR) << "GCM channel request failed. HTTP status: "
145 << response_code;
146 return false;
147 }
148 }
149
150 if (!response_body) {
jianli1235e51c2014-09-08 18:56:41151 LOG(ERROR) << "GCM channel request failed.";
152 return false;
153 }
154
Jian Li74eadb02014-10-16 05:53:56155 // Empty response means to keep the existing values.
Mark Pilgrim7634f5b52018-06-27 19:53:27156 if (response_body->empty()) {
Jian Li74eadb02014-10-16 05:53:56157 callback_.Run(false, false, 0);
158 return true;
159 }
160
fgorski3689f8d2014-10-09 04:39:31161 sync_pb::ExperimentStatusResponse response_proto;
Mark Pilgrim7634f5b52018-06-27 19:53:27162 if (!response_proto.ParseFromString(*response_body)) {
Jian Li74eadb02014-10-16 05:53:56163 LOG(ERROR) << "GCM channel response failed to be parsed as proto.";
jianli1235e51c2014-09-08 18:56:41164 return false;
165 }
166
Mark Pilgrim7634f5b52018-06-27 19:53:27167 ParseResponseProto(response_proto);
168
169 return true;
170}
171
172void GCMChannelStatusRequest::ParseResponseProto(
173 sync_pb::ExperimentStatusResponse response_proto) {
jianli1235e51c2014-09-08 18:56:41174 bool enabled = true;
fgorski3689f8d2014-10-09 04:39:31175 if (response_proto.experiment_size() == 1 &&
176 response_proto.experiment(0).has_gcm_channel() &&
177 response_proto.experiment(0).gcm_channel().has_enabled()) {
178 enabled = response_proto.experiment(0).gcm_channel().enabled();
jianli1235e51c2014-09-08 18:56:41179 }
180
181 int poll_interval_seconds;
182 if (response_proto.has_poll_interval_seconds())
183 poll_interval_seconds = response_proto.poll_interval_seconds();
184 else
185 poll_interval_seconds = kDefaultPollIntervalSeconds;
186 if (poll_interval_seconds < kMinPollIntervalSeconds)
187 poll_interval_seconds = kMinPollIntervalSeconds;
188
Jian Li74eadb02014-10-16 05:53:56189 callback_.Run(true, enabled, poll_interval_seconds);
jianli1235e51c2014-09-08 18:56:41190}
191
192void GCMChannelStatusRequest::RetryWithBackoff(bool update_backoff) {
193 if (update_backoff) {
Mark Pilgrim7634f5b52018-06-27 19:53:27194 simple_url_loader_.reset();
jianli1235e51c2014-09-08 18:56:41195 backoff_entry_.InformOfRequest(false);
196 }
197
198 if (backoff_entry_.ShouldRejectRequest()) {
199 DVLOG(1) << "Delaying GCM channel request for "
200 << backoff_entry_.GetTimeUntilRelease().InMilliseconds()
201 << " ms.";
skyostilb0daa012015-06-02 19:03:48202 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
203 FROM_HERE, base::Bind(&GCMChannelStatusRequest::RetryWithBackoff,
204 weak_ptr_factory_.GetWeakPtr(), false),
jianli1235e51c2014-09-08 18:56:41205 backoff_entry_.GetTimeUntilRelease());
206 return;
207 }
208
209 Start();
210}
211
212} // namespace gcm