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 |