| 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 "sync/internal_api/public/attachments/attachment_uploader_impl.h" | |
| 6 | |
| 7 #include <stdint.h> | |
| 8 #include <string> | |
| 9 #include <utility> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/base64.h" | |
| 13 #include "base/base64url.h" | |
| 14 #include "base/bind.h" | |
| 15 #include "base/location.h" | |
| 16 #include "base/macros.h" | |
| 17 #include "base/memory/weak_ptr.h" | |
| 18 #include "base/metrics/sparse_histogram.h" | |
| 19 #include "base/single_thread_task_runner.h" | |
| 20 #include "base/strings/string_piece.h" | |
| 21 #include "base/strings/stringprintf.h" | |
| 22 #include "base/sys_byteorder.h" | |
| 23 #include "base/threading/non_thread_safe.h" | |
| 24 #include "base/threading/thread_task_runner_handle.h" | |
| 25 #include "google_apis/gaia/gaia_constants.h" | |
| 26 #include "net/base/load_flags.h" | |
| 27 #include "net/http/http_status_code.h" | |
| 28 #include "net/url_request/url_fetcher.h" | |
| 29 #include "net/url_request/url_fetcher_delegate.h" | |
| 30 #include "net/url_request/url_request_status.h" | |
| 31 #include "sync/api/attachments/attachment.h" | |
| 32 #include "sync/protocol/sync.pb.h" | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 const char kContentType[] = "application/octet-stream"; | |
| 37 const char kAttachments[] = "attachments/"; | |
| 38 const char kSyncStoreBirthday[] = "X-Sync-Store-Birthday"; | |
| 39 const char kSyncDataTypeId[] = "X-Sync-Data-Type-Id"; | |
| 40 | |
| 41 } // namespace | |
| 42 | |
| 43 namespace syncer { | |
| 44 | |
| 45 // Encapsulates all the state associated with a single upload. | |
| 46 class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate, | |
| 47 public OAuth2TokenService::Consumer, | |
| 48 public base::NonThreadSafe { | |
| 49 public: | |
| 50 // Construct an UploadState. | |
| 51 // | |
| 52 // UploadState encapsulates the state associated with a single upload. When | |
| 53 // the upload completes, the UploadState object becomes "stopped". | |
| 54 // | |
| 55 // |owner| is a pointer to the object that owns this UploadState. Upon | |
| 56 // completion this object will PostTask to owner's OnUploadStateStopped | |
| 57 // method. | |
| 58 UploadState( | |
| 59 const GURL& upload_url, | |
| 60 const scoped_refptr<net::URLRequestContextGetter>& | |
| 61 url_request_context_getter, | |
| 62 const Attachment& attachment, | |
| 63 const UploadCallback& user_callback, | |
| 64 const std::string& account_id, | |
| 65 const OAuth2TokenService::ScopeSet& scopes, | |
| 66 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider, | |
| 67 const std::string& raw_store_birthday, | |
| 68 const base::WeakPtr<AttachmentUploaderImpl>& owner, | |
| 69 ModelType model_type); | |
| 70 | |
| 71 ~UploadState() override; | |
| 72 | |
| 73 // Returns true if this object is stopped. Once stopped, this object is | |
| 74 // effectively dead and can be destroyed. | |
| 75 bool IsStopped() const; | |
| 76 | |
| 77 // Add |user_callback| to the list of callbacks to be invoked when this upload | |
| 78 // completed. | |
| 79 // | |
| 80 // It is an error to call |AddUserCallback| on a stopped UploadState (see | |
| 81 // |IsStopped|). | |
| 82 void AddUserCallback(const UploadCallback& user_callback); | |
| 83 | |
| 84 // Return the Attachment this object is uploading. | |
| 85 const Attachment& GetAttachment(); | |
| 86 | |
| 87 // URLFetcher implementation. | |
| 88 void OnURLFetchComplete(const net::URLFetcher* source) override; | |
| 89 | |
| 90 // OAuth2TokenService::Consumer. | |
| 91 void OnGetTokenSuccess(const OAuth2TokenService::Request* request, | |
| 92 const std::string& access_token, | |
| 93 const base::Time& expiration_time) override; | |
| 94 void OnGetTokenFailure(const OAuth2TokenService::Request* request, | |
| 95 const GoogleServiceAuthError& error) override; | |
| 96 | |
| 97 private: | |
| 98 typedef std::vector<UploadCallback> UploadCallbackList; | |
| 99 | |
| 100 void GetToken(); | |
| 101 | |
| 102 void StopAndReportResult(const UploadResult& result, | |
| 103 const AttachmentId& attachment_id); | |
| 104 | |
| 105 bool is_stopped_; | |
| 106 GURL upload_url_; | |
| 107 const scoped_refptr<net::URLRequestContextGetter>& | |
| 108 url_request_context_getter_; | |
| 109 Attachment attachment_; | |
| 110 UploadCallbackList user_callbacks_; | |
| 111 std::unique_ptr<net::URLFetcher> fetcher_; | |
| 112 std::string account_id_; | |
| 113 OAuth2TokenService::ScopeSet scopes_; | |
| 114 std::string access_token_; | |
| 115 std::string raw_store_birthday_; | |
| 116 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider_; | |
| 117 // Pointer to the AttachmentUploaderImpl that owns this object. | |
| 118 base::WeakPtr<AttachmentUploaderImpl> owner_; | |
| 119 std::unique_ptr<OAuth2TokenServiceRequest> access_token_request_; | |
| 120 ModelType model_type_; | |
| 121 | |
| 122 DISALLOW_COPY_AND_ASSIGN(UploadState); | |
| 123 }; | |
| 124 | |
| 125 AttachmentUploaderImpl::UploadState::UploadState( | |
| 126 const GURL& upload_url, | |
| 127 const scoped_refptr<net::URLRequestContextGetter>& | |
| 128 url_request_context_getter, | |
| 129 const Attachment& attachment, | |
| 130 const UploadCallback& user_callback, | |
| 131 const std::string& account_id, | |
| 132 const OAuth2TokenService::ScopeSet& scopes, | |
| 133 OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider, | |
| 134 const std::string& raw_store_birthday, | |
| 135 const base::WeakPtr<AttachmentUploaderImpl>& owner, | |
| 136 ModelType model_type) | |
| 137 : OAuth2TokenService::Consumer("attachment-uploader-impl"), | |
| 138 is_stopped_(false), | |
| 139 upload_url_(upload_url), | |
| 140 url_request_context_getter_(url_request_context_getter), | |
| 141 attachment_(attachment), | |
| 142 user_callbacks_(1, user_callback), | |
| 143 account_id_(account_id), | |
| 144 scopes_(scopes), | |
| 145 raw_store_birthday_(raw_store_birthday), | |
| 146 token_service_provider_(token_service_provider), | |
| 147 owner_(owner), | |
| 148 model_type_(model_type) { | |
| 149 DCHECK(upload_url_.is_valid()); | |
| 150 DCHECK(url_request_context_getter_.get()); | |
| 151 DCHECK(!account_id_.empty()); | |
| 152 DCHECK(!scopes_.empty()); | |
| 153 DCHECK(token_service_provider_); | |
| 154 DCHECK(!raw_store_birthday_.empty()); | |
| 155 GetToken(); | |
| 156 } | |
| 157 | |
| 158 AttachmentUploaderImpl::UploadState::~UploadState() { | |
| 159 } | |
| 160 | |
| 161 bool AttachmentUploaderImpl::UploadState::IsStopped() const { | |
| 162 DCHECK(CalledOnValidThread()); | |
| 163 return is_stopped_; | |
| 164 } | |
| 165 | |
| 166 void AttachmentUploaderImpl::UploadState::AddUserCallback( | |
| 167 const UploadCallback& user_callback) { | |
| 168 DCHECK(CalledOnValidThread()); | |
| 169 DCHECK(!is_stopped_); | |
| 170 user_callbacks_.push_back(user_callback); | |
| 171 } | |
| 172 | |
| 173 const Attachment& AttachmentUploaderImpl::UploadState::GetAttachment() { | |
| 174 DCHECK(CalledOnValidThread()); | |
| 175 return attachment_; | |
| 176 } | |
| 177 | |
| 178 void AttachmentUploaderImpl::UploadState::OnURLFetchComplete( | |
| 179 const net::URLFetcher* source) { | |
| 180 DCHECK(CalledOnValidThread()); | |
| 181 if (is_stopped_) { | |
| 182 return; | |
| 183 } | |
| 184 | |
| 185 UploadResult result = UPLOAD_TRANSIENT_ERROR; | |
| 186 AttachmentId attachment_id = attachment_.GetId(); | |
| 187 net::URLRequestStatus status = source->GetStatus(); | |
| 188 const int response_code = source->GetResponseCode(); | |
| 189 UMA_HISTOGRAM_SPARSE_SLOWLY("Sync.Attachments.UploadResponseCode", | |
| 190 status.is_success() ? response_code : status.error()); | |
| 191 if (response_code == net::HTTP_OK) { | |
| 192 result = UPLOAD_SUCCESS; | |
| 193 } else if (response_code == net::HTTP_UNAUTHORIZED) { | |
| 194 // Server tells us we've got a bad token so invalidate it. | |
| 195 OAuth2TokenServiceRequest::InvalidateToken( | |
| 196 token_service_provider_, account_id_, scopes_, access_token_); | |
| 197 // Fail the request, but indicate that it may be successful if retried. | |
| 198 result = UPLOAD_TRANSIENT_ERROR; | |
| 199 } else if (response_code == net::HTTP_FORBIDDEN) { | |
| 200 // User is not allowed to use attachments. Retrying won't help. | |
| 201 result = UPLOAD_UNSPECIFIED_ERROR; | |
| 202 } else if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) { | |
| 203 result = UPLOAD_TRANSIENT_ERROR; | |
| 204 } | |
| 205 StopAndReportResult(result, attachment_id); | |
| 206 } | |
| 207 | |
| 208 void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess( | |
| 209 const OAuth2TokenService::Request* request, | |
| 210 const std::string& access_token, | |
| 211 const base::Time& expiration_time) { | |
| 212 DCHECK(CalledOnValidThread()); | |
| 213 if (is_stopped_) { | |
| 214 return; | |
| 215 } | |
| 216 | |
| 217 DCHECK_EQ(access_token_request_.get(), request); | |
| 218 access_token_request_.reset(); | |
| 219 access_token_ = access_token; | |
| 220 fetcher_ = net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); | |
| 221 ConfigureURLFetcherCommon(fetcher_.get(), access_token_, raw_store_birthday_, | |
| 222 model_type_, url_request_context_getter_.get()); | |
| 223 | |
| 224 const uint32_t crc32c = attachment_.GetCrc32c(); | |
| 225 fetcher_->AddExtraRequestHeader(base::StringPrintf( | |
| 226 "X-Goog-Hash: crc32c=%s", FormatCrc32cHash(crc32c).c_str())); | |
| 227 | |
| 228 // TODO(maniscalco): Is there a better way? Copying the attachment data into | |
| 229 // a string feels wrong given how large attachments may be (several MBs). If | |
| 230 // we may end up switching from URLFetcher to URLRequest, this copy won't be | |
| 231 // necessary. | |
| 232 scoped_refptr<base::RefCountedMemory> memory = attachment_.GetData(); | |
| 233 const std::string upload_content(memory->front_as<char>(), memory->size()); | |
| 234 fetcher_->SetUploadData(kContentType, upload_content); | |
| 235 | |
| 236 fetcher_->Start(); | |
| 237 } | |
| 238 | |
| 239 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure( | |
| 240 const OAuth2TokenService::Request* request, | |
| 241 const GoogleServiceAuthError& error) { | |
| 242 DCHECK(CalledOnValidThread()); | |
| 243 if (is_stopped_) { | |
| 244 return; | |
| 245 } | |
| 246 | |
| 247 DCHECK_EQ(access_token_request_.get(), request); | |
| 248 access_token_request_.reset(); | |
| 249 // TODO(maniscalco): We treat this as a transient error, but it may in fact be | |
| 250 // a very long lived error and require user action. Consider differentiating | |
| 251 // between the causes of GetToken failure and act accordingly. Think about | |
| 252 // the causes of GetToken failure. Are there (bug 412802). | |
| 253 StopAndReportResult(UPLOAD_TRANSIENT_ERROR, attachment_.GetId()); | |
| 254 } | |
| 255 | |
| 256 void AttachmentUploaderImpl::UploadState::GetToken() { | |
| 257 access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart( | |
| 258 token_service_provider_, account_id_, scopes_, this); | |
| 259 } | |
| 260 | |
| 261 void AttachmentUploaderImpl::UploadState::StopAndReportResult( | |
| 262 const UploadResult& result, | |
| 263 const AttachmentId& attachment_id) { | |
| 264 DCHECK(!is_stopped_); | |
| 265 is_stopped_ = true; | |
| 266 UploadCallbackList::const_iterator iter = user_callbacks_.begin(); | |
| 267 UploadCallbackList::const_iterator end = user_callbacks_.end(); | |
| 268 for (; iter != end; ++iter) { | |
| 269 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 270 FROM_HERE, base::Bind(*iter, result, attachment_id)); | |
| 271 } | |
| 272 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 273 FROM_HERE, base::Bind(&AttachmentUploaderImpl::OnUploadStateStopped, | |
| 274 owner_, attachment_id.GetProto().unique_id())); | |
| 275 } | |
| 276 | |
| 277 AttachmentUploaderImpl::AttachmentUploaderImpl( | |
| 278 const GURL& sync_service_url, | |
| 279 const scoped_refptr<net::URLRequestContextGetter>& | |
| 280 url_request_context_getter, | |
| 281 const std::string& account_id, | |
| 282 const OAuth2TokenService::ScopeSet& scopes, | |
| 283 const scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>& | |
| 284 token_service_provider, | |
| 285 const std::string& store_birthday, | |
| 286 ModelType model_type) | |
| 287 : sync_service_url_(sync_service_url), | |
| 288 url_request_context_getter_(url_request_context_getter), | |
| 289 account_id_(account_id), | |
| 290 scopes_(scopes), | |
| 291 token_service_provider_(token_service_provider), | |
| 292 raw_store_birthday_(store_birthday), | |
| 293 model_type_(model_type), | |
| 294 weak_ptr_factory_(this) { | |
| 295 DCHECK(CalledOnValidThread()); | |
| 296 DCHECK(!account_id.empty()); | |
| 297 DCHECK(!scopes.empty()); | |
| 298 DCHECK(token_service_provider_.get()); | |
| 299 DCHECK(!raw_store_birthday_.empty()); | |
| 300 } | |
| 301 | |
| 302 AttachmentUploaderImpl::~AttachmentUploaderImpl() { | |
| 303 DCHECK(CalledOnValidThread()); | |
| 304 } | |
| 305 | |
| 306 void AttachmentUploaderImpl::UploadAttachment(const Attachment& attachment, | |
| 307 const UploadCallback& callback) { | |
| 308 DCHECK(CalledOnValidThread()); | |
| 309 const AttachmentId attachment_id = attachment.GetId(); | |
| 310 const std::string unique_id = attachment_id.GetProto().unique_id(); | |
| 311 DCHECK(!unique_id.empty()); | |
| 312 StateMap::iterator iter = state_map_.find(unique_id); | |
| 313 if (iter != state_map_.end()) { | |
| 314 // We have an old upload request for this attachment... | |
| 315 if (!iter->second->IsStopped()) { | |
| 316 // "join" to it. | |
| 317 DCHECK(attachment.GetData() | |
| 318 ->Equals(iter->second->GetAttachment().GetData())); | |
| 319 iter->second->AddUserCallback(callback); | |
| 320 return; | |
| 321 } else { | |
| 322 // It's stopped so we can't use it. Delete it. | |
| 323 state_map_.erase(iter); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 const GURL url = GetURLForAttachmentId(sync_service_url_, attachment_id); | |
| 328 std::unique_ptr<UploadState> upload_state(new UploadState( | |
| 329 url, url_request_context_getter_, attachment, callback, account_id_, | |
| 330 scopes_, token_service_provider_.get(), raw_store_birthday_, | |
| 331 weak_ptr_factory_.GetWeakPtr(), model_type_)); | |
| 332 state_map_[unique_id] = std::move(upload_state); | |
| 333 } | |
| 334 | |
| 335 // Static. | |
| 336 GURL AttachmentUploaderImpl::GetURLForAttachmentId( | |
| 337 const GURL& sync_service_url, | |
| 338 const AttachmentId& attachment_id) { | |
| 339 std::string path = sync_service_url.path(); | |
| 340 if (path.empty() || *path.rbegin() != '/') { | |
| 341 path += '/'; | |
| 342 } | |
| 343 path += kAttachments; | |
| 344 path += attachment_id.GetProto().unique_id(); | |
| 345 GURL::Replacements replacements; | |
| 346 replacements.SetPathStr(path); | |
| 347 return sync_service_url.ReplaceComponents(replacements); | |
| 348 } | |
| 349 | |
| 350 void AttachmentUploaderImpl::OnUploadStateStopped(const UniqueId& unique_id) { | |
| 351 StateMap::iterator iter = state_map_.find(unique_id); | |
| 352 // Only erase if stopped. Because this method is called asynchronously, it's | |
| 353 // possible that a new request for this same id arrived after the UploadState | |
| 354 // stopped, but before this method was invoked. In that case the UploadState | |
| 355 // in the map might be a new one. | |
| 356 if (iter != state_map_.end() && iter->second->IsStopped()) { | |
| 357 state_map_.erase(iter); | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 std::string AttachmentUploaderImpl::FormatCrc32cHash(uint32_t crc32c) { | |
| 362 const uint32_t crc32c_big_endian = base::HostToNet32(crc32c); | |
| 363 const base::StringPiece raw(reinterpret_cast<const char*>(&crc32c_big_endian), | |
| 364 sizeof(crc32c_big_endian)); | |
| 365 std::string encoded; | |
| 366 base::Base64Encode(raw, &encoded); | |
| 367 return encoded; | |
| 368 } | |
| 369 | |
| 370 void AttachmentUploaderImpl::ConfigureURLFetcherCommon( | |
| 371 net::URLFetcher* fetcher, | |
| 372 const std::string& access_token, | |
| 373 const std::string& raw_store_birthday, | |
| 374 ModelType model_type, | |
| 375 net::URLRequestContextGetter* request_context_getter) { | |
| 376 DCHECK(request_context_getter); | |
| 377 DCHECK(fetcher); | |
| 378 fetcher->SetAutomaticallyRetryOn5xx(false); | |
| 379 fetcher->SetRequestContext(request_context_getter); | |
| 380 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | | |
| 381 net::LOAD_DO_NOT_SEND_COOKIES | | |
| 382 net::LOAD_DISABLE_CACHE); | |
| 383 fetcher->AddExtraRequestHeader(base::StringPrintf( | |
| 384 "%s: Bearer %s", net::HttpRequestHeaders::kAuthorization, | |
| 385 access_token.c_str())); | |
| 386 // Encode the birthday. Birthday is opaque so we assume it could contain | |
| 387 // anything. Encode it so that it's safe to pass as an HTTP header value. | |
| 388 std::string encoded_store_birthday; | |
| 389 base::Base64UrlEncode( | |
| 390 raw_store_birthday, base::Base64UrlEncodePolicy::OMIT_PADDING, | |
| 391 &encoded_store_birthday); | |
| 392 fetcher->AddExtraRequestHeader(base::StringPrintf( | |
| 393 "%s: %s", kSyncStoreBirthday, encoded_store_birthday.c_str())); | |
| 394 | |
| 395 // Use field number to pass ModelType because it's stable and we have server | |
| 396 // code to decode it. | |
| 397 const int field_number = GetSpecificsFieldNumberFromModelType(model_type); | |
| 398 fetcher->AddExtraRequestHeader( | |
| 399 base::StringPrintf("%s: %d", kSyncDataTypeId, field_number)); | |
| 400 } | |
| 401 | |
| 402 } // namespace syncer | |
| OLD | NEW |