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"
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"
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";
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
{
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
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
73 // It is an error to call |AddUserCallback| on a stopped UploadState (see
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
;
91 typedef std::vector
<UploadCallback
> UploadCallbackList
;
95 void StopAndReportResult(const UploadResult
& result
,
96 const AttachmentId
& attachment_id
);
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"),
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
),
138 raw_store_birthday_(raw_store_birthday
),
139 token_service_provider_(token_service_provider
),
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());
151 AttachmentUploaderImpl::UploadState::~UploadState() {
154 bool AttachmentUploaderImpl::UploadState::IsStopped() const {
155 DCHECK(CalledOnValidThread());
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());
171 void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
172 const net::URLFetcher
* source
) {
173 DCHECK(CalledOnValidThread());
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());
210 DCHECK_EQ(access_token_request_
.get(), request
);
211 access_token_request_
.reset();
212 access_token_
= access_token
;
214 net::URLFetcher::Create(upload_url_
, net::URLFetcher::POST
, this));
215 ConfigureURLFetcherCommon(fetcher_
.get(), access_token_
, raw_store_birthday_
,
216 model_type_
, url_request_context_getter_
.get());
218 const uint32_t crc32c
= attachment_
.GetCrc32c();
219 fetcher_
->AddExtraRequestHeader(base::StringPrintf(
220 "X-Goog-Hash: crc32c=%s", FormatCrc32cHash(crc32c
).c_str()));
222 // TODO(maniscalco): Is there a better way? Copying the attachment data into
223 // a string feels wrong given how large attachments may be (several MBs). If
224 // we may end up switching from URLFetcher to URLRequest, this copy won't be
226 scoped_refptr
<base::RefCountedMemory
> memory
= attachment_
.GetData();
227 const std::string
upload_content(memory
->front_as
<char>(), memory
->size());
228 fetcher_
->SetUploadData(kContentType
, upload_content
);
233 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
234 const OAuth2TokenService::Request
* request
,
235 const GoogleServiceAuthError
& error
) {
236 DCHECK(CalledOnValidThread());
241 DCHECK_EQ(access_token_request_
.get(), request
);
242 access_token_request_
.reset();
243 // TODO(maniscalco): We treat this as a transient error, but it may in fact be
244 // a very long lived error and require user action. Consider differentiating
245 // between the causes of GetToken failure and act accordingly. Think about
246 // the causes of GetToken failure. Are there (bug 412802).
247 StopAndReportResult(UPLOAD_TRANSIENT_ERROR
, attachment_
.GetId());
250 void AttachmentUploaderImpl::UploadState::GetToken() {
251 access_token_request_
= OAuth2TokenServiceRequest::CreateAndStart(
252 token_service_provider_
, account_id_
, scopes_
, this);
255 void AttachmentUploaderImpl::UploadState::StopAndReportResult(
256 const UploadResult
& result
,
257 const AttachmentId
& attachment_id
) {
258 DCHECK(!is_stopped_
);
260 UploadCallbackList::const_iterator iter
= user_callbacks_
.begin();
261 UploadCallbackList::const_iterator end
= user_callbacks_
.end();
262 for (; iter
!= end
; ++iter
) {
263 base::MessageLoop::current()->PostTask(
264 FROM_HERE
, base::Bind(*iter
, result
, attachment_id
));
266 base::MessageLoop::current()->PostTask(
268 base::Bind(&AttachmentUploaderImpl::OnUploadStateStopped
,
270 attachment_id
.GetProto().unique_id()));
273 AttachmentUploaderImpl::AttachmentUploaderImpl(
274 const GURL
& sync_service_url
,
275 const scoped_refptr
<net::URLRequestContextGetter
>&
276 url_request_context_getter
,
277 const std::string
& account_id
,
278 const OAuth2TokenService::ScopeSet
& scopes
,
279 const scoped_refptr
<OAuth2TokenServiceRequest::TokenServiceProvider
>&
280 token_service_provider
,
281 const std::string
& store_birthday
,
282 ModelType model_type
)
283 : sync_service_url_(sync_service_url
),
284 url_request_context_getter_(url_request_context_getter
),
285 account_id_(account_id
),
287 token_service_provider_(token_service_provider
),
288 raw_store_birthday_(store_birthday
),
289 model_type_(model_type
),
290 weak_ptr_factory_(this) {
291 DCHECK(CalledOnValidThread());
292 DCHECK(!account_id
.empty());
293 DCHECK(!scopes
.empty());
294 DCHECK(token_service_provider_
.get());
295 DCHECK(!raw_store_birthday_
.empty());
298 AttachmentUploaderImpl::~AttachmentUploaderImpl() {
299 DCHECK(CalledOnValidThread());
302 void AttachmentUploaderImpl::UploadAttachment(const Attachment
& attachment
,
303 const UploadCallback
& callback
) {
304 DCHECK(CalledOnValidThread());
305 const AttachmentId attachment_id
= attachment
.GetId();
306 const std::string unique_id
= attachment_id
.GetProto().unique_id();
307 DCHECK(!unique_id
.empty());
308 StateMap::iterator iter
= state_map_
.find(unique_id
);
309 if (iter
!= state_map_
.end()) {
310 // We have an old upload request for this attachment...
311 if (!iter
->second
->IsStopped()) {
313 DCHECK(attachment
.GetData()
314 ->Equals(iter
->second
->GetAttachment().GetData()));
315 iter
->second
->AddUserCallback(callback
);
318 // It's stopped so we can't use it. Delete it.
319 state_map_
.erase(iter
);
323 const GURL url
= GetURLForAttachmentId(sync_service_url_
, attachment_id
);
324 scoped_ptr
<UploadState
> upload_state(new UploadState(
325 url
, url_request_context_getter_
, attachment
, callback
, account_id_
,
326 scopes_
, token_service_provider_
.get(), raw_store_birthday_
,
327 weak_ptr_factory_
.GetWeakPtr(), model_type_
));
328 state_map_
.add(unique_id
, upload_state
.Pass());
332 GURL
AttachmentUploaderImpl::GetURLForAttachmentId(
333 const GURL
& sync_service_url
,
334 const AttachmentId
& attachment_id
) {
335 std::string path
= sync_service_url
.path();
336 if (path
.empty() || *path
.rbegin() != '/') {
339 path
+= kAttachments
;
340 path
+= attachment_id
.GetProto().unique_id();
341 GURL::Replacements replacements
;
342 replacements
.SetPathStr(path
);
343 return sync_service_url
.ReplaceComponents(replacements
);
346 void AttachmentUploaderImpl::OnUploadStateStopped(const UniqueId
& unique_id
) {
347 StateMap::iterator iter
= state_map_
.find(unique_id
);
348 // Only erase if stopped. Because this method is called asynchronously, it's
349 // possible that a new request for this same id arrived after the UploadState
350 // stopped, but before this method was invoked. In that case the UploadState
351 // in the map might be a new one.
352 if (iter
!= state_map_
.end() && iter
->second
->IsStopped()) {
353 state_map_
.erase(iter
);
357 std::string
AttachmentUploaderImpl::FormatCrc32cHash(uint32_t crc32c
) {
358 const uint32_t crc32c_big_endian
= base::HostToNet32(crc32c
);
359 const base::StringPiece
raw(reinterpret_cast<const char*>(&crc32c_big_endian
),
360 sizeof(crc32c_big_endian
));
362 base::Base64Encode(raw
, &encoded
);
366 void AttachmentUploaderImpl::ConfigureURLFetcherCommon(
367 net::URLFetcher
* fetcher
,
368 const std::string
& access_token
,
369 const std::string
& raw_store_birthday
,
370 ModelType model_type
,
371 net::URLRequestContextGetter
* request_context_getter
) {
372 DCHECK(request_context_getter
);
374 fetcher
->SetAutomaticallyRetryOn5xx(false);
375 fetcher
->SetRequestContext(request_context_getter
);
376 fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES
|
377 net::LOAD_DO_NOT_SEND_COOKIES
|
378 net::LOAD_DISABLE_CACHE
);
379 fetcher
->AddExtraRequestHeader(base::StringPrintf(
380 "%s: Bearer %s", net::HttpRequestHeaders::kAuthorization
,
381 access_token
.c_str()));
382 // Encode the birthday. Birthday is opaque so we assume it could contain
383 // anything. Encode it so that it's safe to pass as an HTTP header value.
384 std::string encoded_store_birthday
;
385 Base64URLSafeEncode(raw_store_birthday
, &encoded_store_birthday
);
386 fetcher
->AddExtraRequestHeader(base::StringPrintf(
387 "%s: %s", kSyncStoreBirthday
, encoded_store_birthday
.c_str()));
389 // Use field number to pass ModelType because it's stable and we have server
390 // code to decode it.
391 const int field_number
= GetSpecificsFieldNumberFromModelType(model_type
);
392 fetcher
->AddExtraRequestHeader(
393 base::StringPrintf("%s: %d", kSyncDataTypeId
, field_number
));
396 void AttachmentUploaderImpl::Base64URLSafeEncode(const std::string
& input
,
397 std::string
* output
) {
398 base::Base64Encode(input
, output
);
399 base::ReplaceChars(*output
, "+", "-", output
);
400 base::ReplaceChars(*output
, "/", "_", output
);
401 base::TrimString(*output
, "=", output
);
404 } // namespace syncer