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
;
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
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
);
232 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
233 const OAuth2TokenService::Request
* request
,
234 const GoogleServiceAuthError
& error
) {
235 DCHECK(CalledOnValidThread());
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_
);
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(
267 base::Bind(&AttachmentUploaderImpl::OnUploadStateStopped
,
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
),
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()) {
312 DCHECK(attachment
.GetData()
313 ->Equals(iter
->second
->GetAttachment().GetData()));
314 iter
->second
->AddUserCallback(callback
);
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());
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() != '/') {
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
));
361 base::Base64Encode(raw
, &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
);
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