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 // Outcome of the response parsing. Note that these enums are consumed by a |
| 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 |