registration_request.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/registration_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/values.h" 12#include "net/base/escape.h" 13#include "net/http/http_request_headers.h" 14#include "net/http/http_status_code.h" 15#include "net/url_request/url_fetcher.h" 16#include "net/url_request/url_request_context_getter.h" 17#include "net/url_request/url_request_status.h" 18#include "url/gurl.h" 19 20namespace gcm { 21 22namespace { 23 24const char kRegistrationURL[] = 25 "https://android.clients.google.com/c2dm/register3"; 26const char kRegistrationRequestContentType[] = 27 "application/x-www-form-urlencoded"; 28 29// Request constants. 30const char kAppIdKey[] = "app"; 31const char kDeviceIdKey[] = "device"; 32const char kLoginHeader[] = "AidLogin"; 33const char kSenderKey[] = "sender"; 34 35// Request validation constants. 36const size_t kMaxSenders = 100; 37 38// Response constants. 39const char kErrorPrefix[] = "Error="; 40const char kTokenPrefix[] = "token="; 41const char kDeviceRegistrationError[] = "PHONE_REGISTRATION_ERROR"; 42const char kAuthenticationFailed[] = "AUTHENTICATION_FAILED"; 43const char kInvalidSender[] = "INVALID_SENDER"; 44const char kInvalidParameters[] = "INVALID_PARAMETERS"; 45 46void BuildFormEncoding(const std::string& key, 47 const std::string& value, 48 std::string* out) { 49 if (!out->empty()) 50 out->append("&"); 51 out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); 52} 53 54// Gets correct status from the error message. 55RegistrationRequest::Status GetStatusFromError(const std::string& error) { 56 // TODO(fgorski): Improve error parsing in case there is nore then just an 57 // Error=ERROR_STRING in response. 58 if (error.find(kDeviceRegistrationError) != std::string::npos) 59 return RegistrationRequest::DEVICE_REGISTRATION_ERROR; 60 if (error.find(kAuthenticationFailed) != std::string::npos) 61 return RegistrationRequest::AUTHENTICATION_FAILED; 62 if (error.find(kInvalidSender) != std::string::npos) 63 return RegistrationRequest::INVALID_SENDER; 64 if (error.find(kInvalidParameters) != std::string::npos) 65 return RegistrationRequest::INVALID_PARAMETERS; 66 return RegistrationRequest::UNKNOWN_ERROR; 67} 68 69// Indicates whether a retry attempt should be made based on the status of the 70// last request. 71bool ShouldRetryWithStatus(RegistrationRequest::Status status) { 72 return status == RegistrationRequest::UNKNOWN_ERROR || 73 status == RegistrationRequest::AUTHENTICATION_FAILED || 74 status == RegistrationRequest::DEVICE_REGISTRATION_ERROR || 75 status == RegistrationRequest::HTTP_NOT_OK || 76 status == RegistrationRequest::URL_FETCHING_FAILED || 77 status == RegistrationRequest::RESPONSE_PARSING_FAILED; 78} 79 80void RecordRegistrationStatusToUMA(RegistrationRequest::Status status) { 81 UMA_HISTOGRAM_ENUMERATION("GCM.RegistrationRequestStatus", status, 82 RegistrationRequest::STATUS_COUNT); 83} 84 85} // namespace 86 87RegistrationRequest::RequestInfo::RequestInfo( 88 uint64 android_id, 89 uint64 security_token, 90 const std::string& app_id, 91 const std::vector<std::string>& sender_ids) 92 : android_id(android_id), 93 security_token(security_token), 94 app_id(app_id), 95 sender_ids(sender_ids) { 96} 97 98RegistrationRequest::RequestInfo::~RequestInfo() {} 99 100RegistrationRequest::RegistrationRequest( 101 const RequestInfo& request_info, 102 const net::BackoffEntry::Policy& backoff_policy, 103 const RegistrationCallback& callback, 104 int max_retry_count, 105 scoped_refptr<net::URLRequestContextGetter> request_context_getter) 106 : callback_(callback), 107 request_info_(request_info), 108 backoff_entry_(&backoff_policy), 109 request_context_getter_(request_context_getter), 110 retries_left_(max_retry_count), 111 weak_ptr_factory_(this) { 112 DCHECK_GE(max_retry_count, 0); 113} 114 115RegistrationRequest::~RegistrationRequest() {} 116 117void RegistrationRequest::Start() { 118 DCHECK(!callback_.is_null()); 119 DCHECK(request_info_.android_id != 0UL); 120 DCHECK(request_info_.security_token != 0UL); 121 DCHECK(0 < request_info_.sender_ids.size() && 122 request_info_.sender_ids.size() <= kMaxSenders); 123 124 DCHECK(!url_fetcher_.get()); 125 url_fetcher_.reset(net::URLFetcher::Create( 126 GURL(kRegistrationURL), net::URLFetcher::POST, this)); 127 url_fetcher_->SetRequestContext(request_context_getter_); 128 129 std::string android_id = base::Uint64ToString(request_info_.android_id); 130 std::string auth_header = 131 std::string(net::HttpRequestHeaders::kAuthorization) + ": " + 132 kLoginHeader + " " + android_id + ":" + 133 base::Uint64ToString(request_info_.security_token); 134 url_fetcher_->SetExtraRequestHeaders(auth_header); 135 136 std::string body; 137 BuildFormEncoding(kAppIdKey, request_info_.app_id, &body); 138 BuildFormEncoding(kDeviceIdKey, android_id, &body); 139 140 std::string senders; 141 for (std::vector<std::string>::const_iterator iter = 142 request_info_.sender_ids.begin(); 143 iter != request_info_.sender_ids.end(); 144 ++iter) { 145 DCHECK(!iter->empty()); 146 if (!senders.empty()) 147 senders.append(","); 148 senders.append(*iter); 149 } 150 BuildFormEncoding(kSenderKey, senders, &body); 151 152 DVLOG(1) << "Performing registration for: " << request_info_.app_id; 153 DVLOG(1) << "Registration request: " << body; 154 url_fetcher_->SetUploadData(kRegistrationRequestContentType, body); 155 url_fetcher_->Start(); 156} 157 158void RegistrationRequest::RetryWithBackoff(bool update_backoff) { 159 if (update_backoff) { 160 DCHECK_GT(retries_left_, 0); 161 --retries_left_; 162 url_fetcher_.reset(); 163 backoff_entry_.InformOfRequest(false); 164 } 165 166 if (backoff_entry_.ShouldRejectRequest()) { 167 DVLOG(1) << "Delaying GCM registration of app: " 168 << request_info_.app_id << ", for " 169 << backoff_entry_.GetTimeUntilRelease().InMilliseconds() 170 << " milliseconds."; 171 base::MessageLoop::current()->PostDelayedTask( 172 FROM_HERE, 173 base::Bind(&RegistrationRequest::RetryWithBackoff, 174 weak_ptr_factory_.GetWeakPtr(), 175 false), 176 backoff_entry_.GetTimeUntilRelease()); 177 return; 178 } 179 180 Start(); 181} 182 183RegistrationRequest::Status RegistrationRequest::ParseResponse( 184 const net::URLFetcher* source, std::string* token) { 185 if (!source->GetStatus().is_success()) { 186 LOG(ERROR) << "URL fetching failed."; 187 return URL_FETCHING_FAILED; 188 } 189 190 std::string response; 191 if (!source->GetResponseAsString(&response)) { 192 LOG(ERROR) << "Failed to parse registration response as a string."; 193 return RESPONSE_PARSING_FAILED; 194 } 195 196 if (source->GetResponseCode() == net::HTTP_OK) { 197 size_t token_pos = response.find(kTokenPrefix); 198 if (token_pos != std::string::npos) { 199 *token = response.substr(token_pos + arraysize(kTokenPrefix) - 1); 200 return SUCCESS; 201 } 202 } 203 204 // If we are able to parse a meaningful known error, let's do so. Some errors 205 // will have HTTP_BAD_REQUEST, some will have HTTP_OK response code. 206 size_t error_pos = response.find(kErrorPrefix); 207 if (error_pos != std::string::npos) { 208 std::string error = response.substr( 209 error_pos + arraysize(kErrorPrefix) - 1); 210 return GetStatusFromError(error); 211 } 212 213 // If we cannot tell what the error is, but at least we know response code was 214 // not OK. 215 if (source->GetResponseCode() != net::HTTP_OK) { 216 DLOG(ERROR) << "URL fetching HTTP response code is not OK. It is " 217 << source->GetResponseCode(); 218 return HTTP_NOT_OK; 219 } 220 221 return UNKNOWN_ERROR; 222} 223 224void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { 225 std::string token; 226 Status status = ParseResponse(source, &token); 227 RecordRegistrationStatusToUMA(status); 228 229 if (ShouldRetryWithStatus(status)) { 230 if (retries_left_ > 0) { 231 RetryWithBackoff(true); 232 return; 233 } 234 235 status = REACHED_MAX_RETRIES; 236 RecordRegistrationStatusToUMA(status); 237 } 238 239 callback_.Run(status, token); 240} 241 242} // namespace gcm 243