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