1 // 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.
5 #include "google_apis/gcm/engine/unregistration_request.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_piece.h"
12 #include "base/values.h"
13 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
14 #include "net/base/escape.h"
15 #include "net/http/http_request_headers.h"
16 #include "net/http/http_status_code.h"
17 #include "net/url_request/url_fetcher.h"
18 #include "net/url_request/url_request_context_getter.h"
19 #include "net/url_request/url_request_status.h"
25 const char kRequestContentType
[] = "application/x-www-form-urlencoded";
28 const char kAppIdKey
[] = "app";
29 const char kDeleteKey
[] = "delete";
30 const char kDeleteValue
[] = "true";
31 const char kDeviceIdKey
[] = "device";
32 const char kLoginHeader
[] = "AidLogin";
33 const char kUnregistrationCallerKey
[] = "gcm_unreg_caller";
34 // We are going to set the value to "false" in order to forcefully unregister
36 const char kUnregistrationCallerValue
[] = "false";
38 // Response constants.
39 const char kDeletedPrefix
[] = "deleted=";
40 const char kErrorPrefix
[] = "Error=";
41 const char kInvalidParameters
[] = "INVALID_PARAMETERS";
44 void BuildFormEncoding(const std::string
& key
,
45 const std::string
& value
,
49 out
->append(key
+ "=" + net::EscapeUrlEncodedData(value
, true));
52 UnregistrationRequest::Status
ParseFetcherResponse(
53 const net::URLFetcher
* source
,
54 std::string request_app_id
) {
55 if (!source
->GetStatus().is_success()) {
56 DVLOG(1) << "Fetcher failed";
57 return UnregistrationRequest::URL_FETCHING_FAILED
;
60 net::HttpStatusCode response_status
= static_cast<net::HttpStatusCode
>(
61 source
->GetResponseCode());
62 if (response_status
!= net::HTTP_OK
) {
63 DVLOG(1) << "HTTP Status code is not OK, but: " << response_status
;
64 if (response_status
== net::HTTP_SERVICE_UNAVAILABLE
)
65 return UnregistrationRequest::SERVICE_UNAVAILABLE
;
66 else if (response_status
== net::HTTP_INTERNAL_SERVER_ERROR
)
67 return UnregistrationRequest::INTERNAL_SERVER_ERROR
;
68 return UnregistrationRequest::HTTP_NOT_OK
;
72 if (!source
->GetResponseAsString(&response
)) {
73 DVLOG(1) << "Failed to get response body.";
74 return UnregistrationRequest::NO_RESPONSE_BODY
;
77 DVLOG(1) << "Parsing unregistration response.";
78 if (response
.find(kDeletedPrefix
) != std::string::npos
) {
79 std::string app_id
= response
.substr(
80 response
.find(kDeletedPrefix
) + arraysize(kDeletedPrefix
) - 1);
81 if (app_id
== request_app_id
)
82 return UnregistrationRequest::SUCCESS
;
83 return UnregistrationRequest::INCORRECT_APP_ID
;
86 if (response
.find(kErrorPrefix
) != std::string::npos
) {
87 std::string error
= response
.substr(
88 response
.find(kErrorPrefix
) + arraysize(kErrorPrefix
) - 1);
89 if (error
== kInvalidParameters
)
90 return UnregistrationRequest::INVALID_PARAMETERS
;
91 return UnregistrationRequest::UNKNOWN_ERROR
;
94 DVLOG(1) << "Not able to parse a meaningful output from response body."
96 return UnregistrationRequest::RESPONSE_PARSING_FAILED
;
101 UnregistrationRequest::RequestInfo::RequestInfo(
103 uint64 security_token
,
104 const std::string
& app_id
)
105 : android_id(android_id
),
106 security_token(security_token
),
110 UnregistrationRequest::RequestInfo::~RequestInfo() {}
112 UnregistrationRequest::UnregistrationRequest(
113 const GURL
& registration_url
,
114 const RequestInfo
& request_info
,
115 const net::BackoffEntry::Policy
& backoff_policy
,
116 const UnregistrationCallback
& callback
,
117 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter
,
118 GCMStatsRecorder
* recorder
)
119 : callback_(callback
),
120 request_info_(request_info
),
121 registration_url_(registration_url
),
122 backoff_entry_(&backoff_policy
),
123 request_context_getter_(request_context_getter
),
125 weak_ptr_factory_(this) {
128 UnregistrationRequest::~UnregistrationRequest() {}
130 void UnregistrationRequest::Start() {
131 DCHECK(!callback_
.is_null());
132 DCHECK(request_info_
.android_id
!= 0UL);
133 DCHECK(request_info_
.security_token
!= 0UL);
134 DCHECK(!url_fetcher_
.get());
136 url_fetcher_
.reset(net::URLFetcher::Create(
137 registration_url_
, net::URLFetcher::POST
, this));
138 url_fetcher_
->SetRequestContext(request_context_getter_
);
140 std::string android_id
= base::Uint64ToString(request_info_
.android_id
);
141 std::string auth_header
=
142 std::string(kLoginHeader
) + " " + android_id
+ ":" +
143 base::Uint64ToString(request_info_
.security_token
);
144 net::HttpRequestHeaders headers
;
145 headers
.SetHeader(net::HttpRequestHeaders::kAuthorization
, auth_header
);
146 headers
.SetHeader(kAppIdKey
, request_info_
.app_id
);
147 url_fetcher_
->SetExtraRequestHeaders(headers
.ToString());
150 BuildFormEncoding(kAppIdKey
, request_info_
.app_id
, &body
);
151 BuildFormEncoding(kDeviceIdKey
, android_id
, &body
);
152 BuildFormEncoding(kDeleteKey
, kDeleteValue
, &body
);
153 BuildFormEncoding(kUnregistrationCallerKey
,
154 kUnregistrationCallerValue
,
157 DVLOG(1) << "Unregistration request: " << body
;
158 url_fetcher_
->SetUploadData(kRequestContentType
, body
);
160 DVLOG(1) << "Performing unregistration for: " << request_info_
.app_id
;
161 recorder_
->RecordUnregistrationSent(request_info_
.app_id
);
162 request_start_time_
= base::TimeTicks::Now();
163 url_fetcher_
->Start();
166 void UnregistrationRequest::RetryWithBackoff(bool update_backoff
) {
167 if (update_backoff
) {
168 url_fetcher_
.reset();
169 backoff_entry_
.InformOfRequest(false);
172 if (backoff_entry_
.ShouldRejectRequest()) {
173 DVLOG(1) << "Delaying GCM unregistration of app: "
174 << request_info_
.app_id
<< ", for "
175 << backoff_entry_
.GetTimeUntilRelease().InMilliseconds()
177 recorder_
->RecordUnregistrationRetryDelayed(
178 request_info_
.app_id
,
179 backoff_entry_
.GetTimeUntilRelease().InMilliseconds());
180 base::MessageLoop::current()->PostDelayedTask(
182 base::Bind(&UnregistrationRequest::RetryWithBackoff
,
183 weak_ptr_factory_
.GetWeakPtr(),
185 backoff_entry_
.GetTimeUntilRelease());
192 void UnregistrationRequest::OnURLFetchComplete(const net::URLFetcher
* source
) {
193 UnregistrationRequest::Status status
=
194 ParseFetcherResponse(source
, request_info_
.app_id
);
196 DVLOG(1) << "UnregistrationRequestStauts: " << status
;
197 UMA_HISTOGRAM_ENUMERATION("GCM.UnregistrationRequestStatus",
199 UNREGISTRATION_STATUS_COUNT
);
200 recorder_
->RecordUnregistrationResponse(request_info_
.app_id
, status
);
202 if (status
== URL_FETCHING_FAILED
||
203 status
== SERVICE_UNAVAILABLE
||
204 status
== INTERNAL_SERVER_ERROR
||
205 status
== INCORRECT_APP_ID
||
206 status
== RESPONSE_PARSING_FAILED
) {
207 RetryWithBackoff(true);
211 // status == SUCCESS || HTTP_NOT_OK || NO_RESPONSE_BODY ||
212 // INVALID_PARAMETERS || UNKNOWN_ERROR
214 if (status
== SUCCESS
) {
215 UMA_HISTOGRAM_COUNTS("GCM.UnregistrationRetryCount",
216 backoff_entry_
.failure_count());
217 UMA_HISTOGRAM_TIMES("GCM.UnregistrationCompleteTime",
218 base::TimeTicks::Now() - request_start_time_
);
221 callback_
.Run(status
);