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