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 "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" 20 21 namespace gcm { 22 23 namespace { 24 25 const char kRequestContentType[] = "application/x-www-form-urlencoded"; 26 27 // Request constants. 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 35 // the application. 36 const char kUnregistrationCallerValue[] = "false"; 37 38 // Response constants. 39 const char kDeletedPrefix[] = "deleted="; 40 const char kErrorPrefix[] = "Error="; 41 const char kInvalidParameters[] = "INVALID_PARAMETERS"; 42 43 44 void BuildFormEncoding(const std::string& key, 45 const std::string& value, 46 std::string* out) { 47 if (!out->empty()) 48 out->append("&"); 49 out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); 50 } 51 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; 58 } 59 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; 69 } 70 71 std::string response; 72 if (!source->GetResponseAsString(&response)) { 73 DVLOG(1) << "Failed to get response body."; 74 return UnregistrationRequest::NO_RESPONSE_BODY; 75 } 76 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; 84 } 85 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; 92 } 93 94 DVLOG(1) << "Not able to parse a meaningful output from response body." 95 << response; 96 return UnregistrationRequest::RESPONSE_PARSING_FAILED; 97 } 98 99 } // namespace 100 101 UnregistrationRequest::RequestInfo::RequestInfo( 102 uint64 android_id, 103 uint64 security_token, 104 const std::string& app_id) 105 : android_id(android_id), 106 security_token(security_token), 107 app_id(app_id) { 108 } 109 110 UnregistrationRequest::RequestInfo::~RequestInfo() {} 111 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), 124 recorder_(recorder), 125 weak_ptr_factory_(this) { 126 } 127 128 UnregistrationRequest::~UnregistrationRequest() {} 129 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()); 135 136 url_fetcher_.reset(net::URLFetcher::Create( 137 registration_url_, net::URLFetcher::POST, this)); 138 url_fetcher_->SetRequestContext(request_context_getter_); 139 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()); 148 149 std::string body; 150 BuildFormEncoding(kAppIdKey, request_info_.app_id, &body); 151 BuildFormEncoding(kDeviceIdKey, android_id, &body); 152 BuildFormEncoding(kDeleteKey, kDeleteValue, &body); 153 BuildFormEncoding(kUnregistrationCallerKey, 154 kUnregistrationCallerValue, 155 &body); 156 157 DVLOG(1) << "Unregistration request: " << body; 158 url_fetcher_->SetUploadData(kRequestContentType, body); 159 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(); 164 } 165 166 void UnregistrationRequest::RetryWithBackoff(bool update_backoff) { 167 if (update_backoff) { 168 url_fetcher_.reset(); 169 backoff_entry_.InformOfRequest(false); 170 } 171 172 if (backoff_entry_.ShouldRejectRequest()) { 173 DVLOG(1) << "Delaying GCM unregistration of app: " 174 << request_info_.app_id << ", for " 175 << backoff_entry_.GetTimeUntilRelease().InMilliseconds() 176 << " milliseconds."; 177 recorder_->RecordUnregistrationRetryDelayed( 178 request_info_.app_id, 179 backoff_entry_.GetTimeUntilRelease().InMilliseconds()); 180 base::MessageLoop::current()->PostDelayedTask( 181 FROM_HERE, 182 base::Bind(&UnregistrationRequest::RetryWithBackoff, 183 weak_ptr_factory_.GetWeakPtr(), 184 false), 185 backoff_entry_.GetTimeUntilRelease()); 186 return; 187 } 188 189 Start(); 190 } 191 192 void UnregistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { 193 UnregistrationRequest::Status status = 194 ParseFetcherResponse(source, request_info_.app_id); 195 196 DVLOG(1) << "UnregistrationRequestStauts: " << status; 197 UMA_HISTOGRAM_ENUMERATION("GCM.UnregistrationRequestStatus", 198 status, 199 UNREGISTRATION_STATUS_COUNT); 200 recorder_->RecordUnregistrationResponse(request_info_.app_id, status); 201 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); 208 return; 209 } 210 211 // status == SUCCESS || HTTP_NOT_OK || NO_RESPONSE_BODY || 212 // INVALID_PARAMETERS || UNKNOWN_ERROR 213 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_); 219 } 220 221 callback_.Run(status); 222 } 223 224 } // namespace gcm 225