• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/bind.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/threading/non_thread_safe.h"
10 #include "google_apis/gaia/gaia_constants.h"
11 #include "net/base/load_flags.h"
12 #include "net/http/http_status_code.h"
13 #include "net/url_request/url_fetcher.h"
14 #include "net/url_request/url_fetcher_delegate.h"
15 #include "sync/api/attachments/attachment.h"
16 #include "sync/protocol/sync.pb.h"
17 #include "url/gurl.h"
18 
19 namespace {
20 
21 const char kContentType[] = "application/octet-stream";
22 
23 }  // namespace
24 
25 namespace syncer {
26 
27 // Encapsulates all the state associated with a single upload.
28 class AttachmentUploaderImpl::UploadState : public net::URLFetcherDelegate,
29                                             public OAuth2TokenService::Consumer,
30                                             public base::NonThreadSafe {
31  public:
32   // Construct an UploadState.
33   //
34   // |owner| is a pointer to the object that will own (and must outlive!) this
35   // |UploadState.
36   UploadState(
37       const GURL& upload_url,
38       const scoped_refptr<net::URLRequestContextGetter>&
39           url_request_context_getter,
40       const Attachment& attachment,
41       const UploadCallback& user_callback,
42       const std::string& account_id,
43       const OAuth2TokenService::ScopeSet& scopes,
44       OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
45       AttachmentUploaderImpl* owner);
46 
47   virtual ~UploadState();
48 
49   // Add |user_callback| to the list of callbacks to be invoked when this upload
50   // completed.
51   void AddUserCallback(const UploadCallback& user_callback);
52 
53   // Return the Attachment this object is uploading.
54   const Attachment& GetAttachment();
55 
56   // URLFetcher implementation.
57   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
58 
59   // OAuth2TokenService::Consumer.
60   virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
61                                  const std::string& access_token,
62                                  const base::Time& expiration_time) OVERRIDE;
63   virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
64                                  const GoogleServiceAuthError& error) OVERRIDE;
65 
66  private:
67   typedef std::vector<UploadCallback> UploadCallbackList;
68 
69   void GetToken();
70 
71   void ReportResult(const UploadResult& result,
72                     const AttachmentId& attachment_id);
73 
74   GURL upload_url_;
75   const scoped_refptr<net::URLRequestContextGetter>&
76       url_request_context_getter_;
77   Attachment attachment_;
78   UploadCallbackList user_callbacks_;
79   scoped_ptr<net::URLFetcher> fetcher_;
80   std::string account_id_;
81   OAuth2TokenService::ScopeSet scopes_;
82   std::string access_token_;
83   OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider_;
84   // Pointer to the AttachmentUploaderImpl that owns this object.
85   AttachmentUploaderImpl* owner_;
86   scoped_ptr<OAuth2TokenServiceRequest> access_token_request_;
87 
88   DISALLOW_COPY_AND_ASSIGN(UploadState);
89 };
90 
UploadState(const GURL & upload_url,const scoped_refptr<net::URLRequestContextGetter> & url_request_context_getter,const Attachment & attachment,const UploadCallback & user_callback,const std::string & account_id,const OAuth2TokenService::ScopeSet & scopes,OAuth2TokenServiceRequest::TokenServiceProvider * token_service_provider,AttachmentUploaderImpl * owner)91 AttachmentUploaderImpl::UploadState::UploadState(
92     const GURL& upload_url,
93     const scoped_refptr<net::URLRequestContextGetter>&
94         url_request_context_getter,
95     const Attachment& attachment,
96     const UploadCallback& user_callback,
97     const std::string& account_id,
98     const OAuth2TokenService::ScopeSet& scopes,
99     OAuth2TokenServiceRequest::TokenServiceProvider* token_service_provider,
100     AttachmentUploaderImpl* owner)
101     : OAuth2TokenService::Consumer("attachment-uploader-impl"),
102       upload_url_(upload_url),
103       url_request_context_getter_(url_request_context_getter),
104       attachment_(attachment),
105       user_callbacks_(1, user_callback),
106       account_id_(account_id),
107       scopes_(scopes),
108       token_service_provider_(token_service_provider),
109       owner_(owner) {
110   DCHECK(upload_url_.is_valid());
111   DCHECK(url_request_context_getter_);
112   DCHECK(!account_id_.empty());
113   DCHECK(!scopes_.empty());
114   DCHECK(token_service_provider_);
115   DCHECK(owner_);
116   GetToken();
117 }
118 
~UploadState()119 AttachmentUploaderImpl::UploadState::~UploadState() {
120 }
121 
AddUserCallback(const UploadCallback & user_callback)122 void AttachmentUploaderImpl::UploadState::AddUserCallback(
123     const UploadCallback& user_callback) {
124   DCHECK(CalledOnValidThread());
125   user_callbacks_.push_back(user_callback);
126 }
127 
GetAttachment()128 const Attachment& AttachmentUploaderImpl::UploadState::GetAttachment() {
129   DCHECK(CalledOnValidThread());
130   return attachment_;
131 }
132 
OnURLFetchComplete(const net::URLFetcher * source)133 void AttachmentUploaderImpl::UploadState::OnURLFetchComplete(
134     const net::URLFetcher* source) {
135   DCHECK(CalledOnValidThread());
136   UploadResult result = UPLOAD_UNSPECIFIED_ERROR;
137   AttachmentId attachment_id = attachment_.GetId();
138   if (source->GetResponseCode() == net::HTTP_OK) {
139     result = UPLOAD_SUCCESS;
140     // TODO(maniscalco): Update the attachment id with server address
141     // information before passing it to the callback (bug 371522).
142   } else if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
143     // TODO(maniscalco): One possibility is that we received a 401 because our
144     // access token has expired.  We should probably fetch a new access token
145     // and retry this upload before giving up and reporting failure to our
146     // caller (bug 380437).
147     OAuth2TokenServiceRequest::InvalidateToken(
148         token_service_provider_, account_id_, scopes_, access_token_);
149   } else {
150     // TODO(maniscalco): Once the protocol is better defined, deal with the
151     // various HTTP response codes we may encounter.
152   }
153   ReportResult(result, attachment_id);
154 }
155 
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)156 void AttachmentUploaderImpl::UploadState::OnGetTokenSuccess(
157     const OAuth2TokenService::Request* request,
158     const std::string& access_token,
159     const base::Time& expiration_time) {
160   DCHECK_EQ(access_token_request_.get(), request);
161   access_token_request_.reset();
162   access_token_ = access_token;
163   fetcher_.reset(
164       net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this));
165   fetcher_->SetRequestContext(url_request_context_getter_.get());
166   // TODO(maniscalco): Is there a better way?  Copying the attachment data into
167   // a string feels wrong given how large attachments may be (several MBs).  If
168   // we may end up switching from URLFetcher to URLRequest, this copy won't be
169   // necessary.
170   scoped_refptr<base::RefCountedMemory> memory = attachment_.GetData();
171   const std::string upload_content(memory->front_as<char>(), memory->size());
172   fetcher_->SetUploadData(kContentType, upload_content);
173   const std::string auth_header("Authorization: Bearer " + access_token_);
174   fetcher_->AddExtraRequestHeader(auth_header);
175   fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
176                          net::LOAD_DO_NOT_SEND_COOKIES |
177                          net::LOAD_DISABLE_CACHE);
178   // TODO(maniscalco): Set an appropriate headers (User-Agent, Content-type, and
179   // Content-length) on the request and include the content's MD5,
180   // AttachmentId's unique_id and the "sync birthday" (bug 371521).
181   fetcher_->Start();
182 }
183 
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)184 void AttachmentUploaderImpl::UploadState::OnGetTokenFailure(
185     const OAuth2TokenService::Request* request,
186     const GoogleServiceAuthError& error) {
187   DCHECK_EQ(access_token_request_.get(), request);
188   access_token_request_.reset();
189   ReportResult(UPLOAD_UNSPECIFIED_ERROR, attachment_.GetId());
190 }
191 
GetToken()192 void AttachmentUploaderImpl::UploadState::GetToken() {
193   access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart(
194       token_service_provider_, account_id_, scopes_, this);
195 }
196 
ReportResult(const UploadResult & result,const AttachmentId & attachment_id)197 void AttachmentUploaderImpl::UploadState::ReportResult(
198     const UploadResult& result,
199     const AttachmentId& attachment_id) {
200   UploadCallbackList::const_iterator iter = user_callbacks_.begin();
201   UploadCallbackList::const_iterator end = user_callbacks_.end();
202   for (; iter != end; ++iter) {
203     base::MessageLoop::current()->PostTask(
204         FROM_HERE, base::Bind(*iter, result, attachment_id));
205   }
206   // Destroy this object and return immediately.
207   owner_->DeleteUploadStateFor(attachment_.GetId().GetProto().unique_id());
208   return;
209 }
210 
AttachmentUploaderImpl(const std::string & url_prefix,const scoped_refptr<net::URLRequestContextGetter> & url_request_context_getter,const std::string & account_id,const OAuth2TokenService::ScopeSet & scopes,scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider> token_service_provider)211 AttachmentUploaderImpl::AttachmentUploaderImpl(
212     const std::string& url_prefix,
213     const scoped_refptr<net::URLRequestContextGetter>&
214         url_request_context_getter,
215     const std::string& account_id,
216     const OAuth2TokenService::ScopeSet& scopes,
217     scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
218         token_service_provider)
219     : url_prefix_(url_prefix),
220       url_request_context_getter_(url_request_context_getter),
221       account_id_(account_id),
222       scopes_(scopes),
223       token_service_provider_(token_service_provider.Pass()) {
224   DCHECK(CalledOnValidThread());
225   DCHECK(token_service_provider_);
226 }
227 
~AttachmentUploaderImpl()228 AttachmentUploaderImpl::~AttachmentUploaderImpl() {
229   DCHECK(CalledOnValidThread());
230 }
231 
UploadAttachment(const Attachment & attachment,const UploadCallback & callback)232 void AttachmentUploaderImpl::UploadAttachment(const Attachment& attachment,
233                                               const UploadCallback& callback) {
234   DCHECK(CalledOnValidThread());
235   const AttachmentId attachment_id = attachment.GetId();
236   const std::string unique_id = attachment_id.GetProto().unique_id();
237   DCHECK(!unique_id.empty());
238   StateMap::iterator iter = state_map_.find(unique_id);
239   if (iter == state_map_.end()) {
240     const GURL url = GetUploadURLForAttachmentId(attachment_id);
241     scoped_ptr<UploadState> upload_state(
242         new UploadState(url,
243                         url_request_context_getter_,
244                         attachment,
245                         callback,
246                         account_id_,
247                         scopes_,
248                         token_service_provider_.get(),
249                         this));
250     state_map_.add(unique_id, upload_state.Pass());
251   } else {
252     DCHECK(
253         attachment.GetData()->Equals(iter->second->GetAttachment().GetData()));
254     // We already have an upload for this attachment.  "Join" it.
255     iter->second->AddUserCallback(callback);
256   }
257 }
258 
GetUploadURLForAttachmentId(const AttachmentId & attachment_id) const259 GURL AttachmentUploaderImpl::GetUploadURLForAttachmentId(
260     const AttachmentId& attachment_id) const {
261   return GURL(url_prefix_ + attachment_id.GetProto().unique_id());
262 }
263 
DeleteUploadStateFor(const UniqueId & unique_id)264 void AttachmentUploaderImpl::DeleteUploadStateFor(const UniqueId& unique_id) {
265   state_map_.erase(unique_id);
266 }
267 
268 }  // namespace syncer
269