OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "google_apis/gcm/engine/unregistration_request.h" | |
6 | |
7 #include "base/bind.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 "net/base/escape.h" | |
14 #include "net/http/http_request_headers.h" | |
15 #include "net/http/http_status_code.h" | |
16 #include "net/url_request/url_fetcher.h" | |
17 #include "net/url_request/url_request_context_getter.h" | |
18 #include "net/url_request/url_request_status.h" | |
19 #include "url/gurl.h" | |
20 | |
21 namespace gcm { | |
22 | |
23 namespace { | |
24 | |
25 const char kRegistrationURL[] = | |
26 "https://android.clients.google.com/c2dm/register3"; | |
27 const char kRequestContentType[] = "application/x-www-form-urlencoded"; | |
28 | |
29 // Request constants. | |
30 const char kAppIdKey[] = "app"; | |
31 const char kDeleteKey[] = "delete"; | |
32 const char kDeleteValue[] = "true"; | |
33 const char kDeviceIdKey[] = "device"; | |
34 const char kLoginHeader[] = "AidLogin"; | |
35 const char kUnregistrationCallerKey[] = "gcm_unreg_caller"; | |
36 // We are going to set the value to "false" in order to forcefully unregister | |
37 // the application. | |
38 const char kUnregistrationCallerValue[] = "false"; | |
39 | |
40 // Response constants. | |
41 const char kDeletedPrefix[] = "deleted="; | |
42 const char kErrorPrefix[] = "Error="; | |
43 const char kInvalidParameters[] = "INVALID_PARAMETERS"; | |
44 | |
45 // Outcomens of the response parsing. Note that these enums are consumed by a | |
Nicolas Zea
2014/02/14 19:05:05
Outcome
fgorski
2014/02/14 19:56:34
Done.
| |
46 // histogram, so ordering should not be modified. | |
47 enum UnregistrationRequestStatus { | |
48 SUCCESS, // Unregistration completed successfully. | |
49 URL_FETCHING_FAILED, // URL fetching failed. | |
50 NO_RESPONSE_BODY, // No response body. | |
51 RESPONSE_PARSING_FAILED, // Failed to parse a meaningful output from response | |
52 // body. | |
53 INCORRECT_APP_ID, // App ID returned by the fetcher does not match | |
54 // request. | |
55 INVALID_PARAMETERS, // Request parameters were invalid. | |
56 SERVICE_UNAVAILABLE, // Unregistration service unavailable. | |
57 INTERNAL_SERVER_ERROR, // Internal server error happened during request. | |
58 HTTP_NOT_OK, // HTTP response code was not OK. | |
59 UNKNOWN_ERROR, // Unknown error. | |
60 // NOTE: Always keep this entry at the end. Add new status types only | |
61 // immediately above this line. Make sure to update the corresponding | |
62 // histogram enum accordingly. | |
63 UNREGISTRATION_STATUS_COUNT, | |
64 }; | |
65 | |
66 void BuildFormEncoding(const std::string& key, | |
67 const std::string& value, | |
68 std::string* out) { | |
69 if (!out->empty()) | |
70 out->append("&"); | |
71 out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); | |
72 } | |
73 | |
74 UnregistrationRequestStatus ParseFetcherResponse(const net::URLFetcher* source, | |
75 std::string request_app_id) { | |
76 if (!source->GetStatus().is_success()) { | |
77 DVLOG(1) << "Fetcher failed"; | |
78 return URL_FETCHING_FAILED; | |
79 } | |
80 | |
81 net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>( | |
82 source->GetResponseCode()); | |
83 if (response_status != net::HTTP_OK) { | |
84 DVLOG(1) << "HTTP Status code is not OK, but: " << response_status; | |
85 if (response_status == net::HTTP_SERVICE_UNAVAILABLE) | |
86 return SERVICE_UNAVAILABLE; | |
87 else if (response_status == net::HTTP_INTERNAL_SERVER_ERROR) | |
88 return INTERNAL_SERVER_ERROR; | |
89 return HTTP_NOT_OK; | |
90 } | |
91 | |
92 std::string response; | |
93 if (!source->GetResponseAsString(&response)) { | |
94 DVLOG(1) << "Failed to get response body."; | |
95 return NO_RESPONSE_BODY; | |
96 } | |
97 | |
98 DVLOG(1) << "Parsing unregistration response."; | |
99 if (response.find(kDeletedPrefix) != std::string::npos) { | |
100 std::string app_id = response.substr( | |
101 response.find(kDeletedPrefix) + arraysize(kDeletedPrefix) - 1); | |
102 if (app_id == request_app_id) | |
103 return SUCCESS; | |
104 return INCORRECT_APP_ID; | |
105 } | |
106 | |
107 if (response.find(kErrorPrefix) != std::string::npos) { | |
108 std::string error = response.substr( | |
109 response.find(kErrorPrefix) + arraysize(kErrorPrefix) - 1); | |
110 if (error == kInvalidParameters) | |
111 return INVALID_PARAMETERS; | |
112 return UNKNOWN_ERROR; | |
113 } | |
114 | |
115 DVLOG(1) << "Not able to parse a meaningful output from response body." | |
116 << response; | |
117 return RESPONSE_PARSING_FAILED; | |
118 } | |
119 | |
120 } // namespace | |
121 | |
122 UnregistrationRequest::RequestInfo::RequestInfo( | |
123 uint64 android_id, | |
124 uint64 security_token, | |
125 const std::string& app_id) | |
126 : android_id(android_id), | |
127 security_token(security_token), | |
128 app_id(app_id) { | |
129 } | |
130 | |
131 UnregistrationRequest::RequestInfo::~RequestInfo() {} | |
132 | |
133 UnregistrationRequest::UnregistrationRequest( | |
134 const RequestInfo& request_info, | |
135 const net::BackoffEntry::Policy& backoff_policy, | |
136 const UnregistrationCallback& callback, | |
137 scoped_refptr<net::URLRequestContextGetter> request_context_getter) | |
138 : callback_(callback), | |
139 request_info_(request_info), | |
140 backoff_entry_(&backoff_policy), | |
141 request_context_getter_(request_context_getter), | |
142 weak_ptr_factory_(this) { | |
143 } | |
144 | |
145 UnregistrationRequest::~UnregistrationRequest() {} | |
146 | |
147 void UnregistrationRequest::Start() { | |
148 DCHECK(!callback_.is_null()); | |
149 DCHECK(request_info_.android_id != 0UL); | |
150 DCHECK(request_info_.security_token != 0UL); | |
151 DCHECK(!url_fetcher_.get()); | |
152 | |
153 url_fetcher_.reset(net::URLFetcher::Create( | |
154 GURL(kRegistrationURL), net::URLFetcher::DELETE_REQUEST, this)); | |
155 url_fetcher_->SetRequestContext(request_context_getter_); | |
156 | |
157 std::string android_id = base::Uint64ToString(request_info_.android_id); | |
158 std::string auth_header = | |
159 std::string(kLoginHeader) + " " + android_id + ":" + | |
160 base::Uint64ToString(request_info_.security_token); | |
161 net::HttpRequestHeaders headers; | |
162 headers.SetHeader(net::HttpRequestHeaders::kAuthorization, auth_header); | |
163 headers.SetHeader(kAppIdKey, request_info_.app_id); | |
164 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); | |
165 | |
166 std::string body; | |
167 BuildFormEncoding(kAppIdKey, request_info_.app_id, &body); | |
168 BuildFormEncoding(kDeviceIdKey, android_id, &body); | |
169 BuildFormEncoding(kDeleteKey, kDeleteValue, &body); | |
170 BuildFormEncoding(kUnregistrationCallerKey, | |
171 kUnregistrationCallerValue, | |
172 &body); | |
173 | |
174 DVLOG(1) << "Unregistration request: " << body; | |
175 url_fetcher_->SetUploadData(kRequestContentType, body); | |
176 | |
177 DVLOG(1) << "Performing unregistration for: " << request_info_.app_id; | |
178 url_fetcher_->Start(); | |
179 } | |
180 | |
181 void UnregistrationRequest::RetryWithBackoff(bool update_backoff) { | |
182 if (update_backoff) { | |
183 url_fetcher_.reset(); | |
184 backoff_entry_.InformOfRequest(false); | |
185 } | |
186 | |
187 if (backoff_entry_.ShouldRejectRequest()) { | |
188 DVLOG(1) << "Delaying GCM unregistration of app: " | |
189 << request_info_.app_id << ", for " | |
190 << backoff_entry_.GetTimeUntilRelease().InMilliseconds() | |
191 << " milliseconds."; | |
192 base::MessageLoop::current()->PostDelayedTask( | |
193 FROM_HERE, | |
194 base::Bind(&UnregistrationRequest::RetryWithBackoff, | |
195 weak_ptr_factory_.GetWeakPtr(), | |
196 false), | |
197 backoff_entry_.GetTimeUntilRelease()); | |
198 return; | |
199 } | |
200 | |
201 Start(); | |
202 } | |
203 | |
204 void UnregistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { | |
205 UnregistrationRequestStatus status = | |
206 ParseFetcherResponse(source, request_info_.app_id); | |
207 | |
208 DVLOG(1) << "UnregistrationRequestStauts: " << status; | |
209 UMA_HISTOGRAM_ENUMERATION("GCM.UnregistrationRequestStatus", | |
210 status, | |
211 UNREGISTRATION_STATUS_COUNT); | |
212 | |
213 if (status == URL_FETCHING_FAILED || | |
214 status == SERVICE_UNAVAILABLE || | |
215 status == INTERNAL_SERVER_ERROR || | |
216 status == INCORRECT_APP_ID || | |
217 status == RESPONSE_PARSING_FAILED) { | |
218 RetryWithBackoff(true); | |
219 } else { | |
220 // status == SUCCESS || HTTP_NOT_OK || NO_RESPONSE_BODY || | |
221 // INVALID_PARAMETERS || UNKNOWN_ERROR | |
222 callback_.Run(status == SUCCESS); | |
223 } | |
224 } | |
225 | |
226 } // namespace gcm | |
OLD | NEW |