Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / sync / internal_api / attachments / attachment_downloader_impl.cc
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_downloader_impl.h"
6
7 #include "base/bind.h"
8 #include "base/message_loop/message_loop.h"
9 #include "net/base/load_flags.h"
10 #include "net/http/http_response_headers.h"
11 #include "net/http/http_status_code.h"
12 #include "net/http/http_util.h"
13 #include "net/url_request/url_fetcher.h"
14 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
15 #include "sync/protocol/sync.pb.h"
16 #include "url/gurl.h"
17
18 namespace syncer {
19
20 struct AttachmentDownloaderImpl::DownloadState {
21  public:
22   DownloadState(const AttachmentId& attachment_id,
23                 const AttachmentUrl& attachment_url);
24
25   AttachmentId attachment_id;
26   AttachmentUrl attachment_url;
27   // |access_token| needed to invalidate if downloading attachment fails with
28   // HTTP_UNAUTHORIZED.
29   std::string access_token;
30   scoped_ptr<net::URLFetcher> url_fetcher;
31   std::vector<DownloadCallback> user_callbacks;
32 };
33
34 AttachmentDownloaderImpl::DownloadState::DownloadState(
35     const AttachmentId& attachment_id,
36     const AttachmentUrl& attachment_url)
37     : attachment_id(attachment_id), attachment_url(attachment_url) {
38 }
39
40 AttachmentDownloaderImpl::AttachmentDownloaderImpl(
41     const GURL& sync_service_url,
42     const scoped_refptr<net::URLRequestContextGetter>&
43         url_request_context_getter,
44     const std::string& account_id,
45     const OAuth2TokenService::ScopeSet& scopes,
46     const scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>&
47         token_service_provider)
48     : OAuth2TokenService::Consumer("attachment-downloader-impl"),
49       sync_service_url_(sync_service_url),
50       url_request_context_getter_(url_request_context_getter),
51       account_id_(account_id),
52       oauth2_scopes_(scopes),
53       token_service_provider_(token_service_provider) {
54   DCHECK(!account_id.empty());
55   DCHECK(!scopes.empty());
56   DCHECK(token_service_provider_.get());
57   DCHECK(url_request_context_getter_.get());
58 }
59
60 AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
61 }
62
63 void AttachmentDownloaderImpl::DownloadAttachment(
64     const AttachmentId& attachment_id,
65     const DownloadCallback& callback) {
66   DCHECK(CalledOnValidThread());
67
68   AttachmentUrl url = AttachmentUploaderImpl::GetURLForAttachmentId(
69                           sync_service_url_, attachment_id).spec();
70
71   StateMap::iterator iter = state_map_.find(url);
72   if (iter == state_map_.end()) {
73     // There is no request started for this attachment id. Let's create
74     // DownloadState and request access token for it.
75     scoped_ptr<DownloadState> new_download_state(
76         new DownloadState(attachment_id, url));
77     iter = state_map_.add(url, new_download_state.Pass()).first;
78     RequestAccessToken(iter->second);
79   }
80   DownloadState* download_state = iter->second;
81   DCHECK(download_state->attachment_id == attachment_id);
82   download_state->user_callbacks.push_back(callback);
83 }
84
85 void AttachmentDownloaderImpl::OnGetTokenSuccess(
86     const OAuth2TokenService::Request* request,
87     const std::string& access_token,
88     const base::Time& expiration_time) {
89   DCHECK(CalledOnValidThread());
90   DCHECK(request == access_token_request_.get());
91   access_token_request_.reset();
92   StateList::const_iterator iter;
93   // Start downloads for all download requests waiting for access token.
94   for (iter = requests_waiting_for_access_token_.begin();
95        iter != requests_waiting_for_access_token_.end();
96        ++iter) {
97     DownloadState* download_state = *iter;
98     download_state->access_token = access_token;
99     download_state->url_fetcher =
100         CreateFetcher(download_state->attachment_url, access_token).Pass();
101     download_state->url_fetcher->Start();
102   }
103   requests_waiting_for_access_token_.clear();
104 }
105
106 void AttachmentDownloaderImpl::OnGetTokenFailure(
107     const OAuth2TokenService::Request* request,
108     const GoogleServiceAuthError& error) {
109   DCHECK(CalledOnValidThread());
110   DCHECK(request == access_token_request_.get());
111   access_token_request_.reset();
112   StateList::const_iterator iter;
113   // Without access token all downloads fail.
114   for (iter = requests_waiting_for_access_token_.begin();
115        iter != requests_waiting_for_access_token_.end();
116        ++iter) {
117     DownloadState* download_state = *iter;
118     scoped_refptr<base::RefCountedString> null_attachment_data;
119     ReportResult(
120         *download_state, DOWNLOAD_TRANSIENT_ERROR, null_attachment_data);
121     DCHECK(state_map_.find(download_state->attachment_url) != state_map_.end());
122     state_map_.erase(download_state->attachment_url);
123   }
124   requests_waiting_for_access_token_.clear();
125 }
126
127 void AttachmentDownloaderImpl::OnURLFetchComplete(
128     const net::URLFetcher* source) {
129   DCHECK(CalledOnValidThread());
130
131   // Find DownloadState by url.
132   AttachmentUrl url = source->GetOriginalURL().spec();
133   StateMap::iterator iter = state_map_.find(url);
134   DCHECK(iter != state_map_.end());
135   const DownloadState& download_state = *iter->second;
136   DCHECK(source == download_state.url_fetcher.get());
137
138   DownloadResult result = DOWNLOAD_TRANSIENT_ERROR;
139   scoped_refptr<base::RefCountedString> attachment_data;
140
141   const int response_code = source->GetResponseCode();
142   if (response_code == net::HTTP_OK) {
143     std::string data_as_string;
144     source->GetResponseAsString(&data_as_string);
145     if (VerifyHashIfPresent(*source, data_as_string)) {
146       result = DOWNLOAD_SUCCESS;
147       attachment_data = base::RefCountedString::TakeString(&data_as_string);
148     } else {
149       // TODO(maniscalco): Test me!
150       result = DOWNLOAD_TRANSIENT_ERROR;
151     }
152   } else if (response_code == net::HTTP_UNAUTHORIZED) {
153     // Server tells us we've got a bad token so invalidate it.
154     OAuth2TokenServiceRequest::InvalidateToken(token_service_provider_.get(),
155                                                account_id_,
156                                                oauth2_scopes_,
157                                                download_state.access_token);
158     // Fail the request, but indicate that it may be successful if retried.
159     result = DOWNLOAD_TRANSIENT_ERROR;
160   } else if (response_code == net::HTTP_FORBIDDEN) {
161     // User is not allowed to use attachments.  Retrying won't help.
162     result = DOWNLOAD_UNSPECIFIED_ERROR;
163   } else if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) {
164     result = DOWNLOAD_TRANSIENT_ERROR;
165   }
166   ReportResult(download_state, result, attachment_data);
167   state_map_.erase(iter);
168 }
169
170 scoped_ptr<net::URLFetcher> AttachmentDownloaderImpl::CreateFetcher(
171     const AttachmentUrl& url,
172     const std::string& access_token) {
173   scoped_ptr<net::URLFetcher> url_fetcher(
174       net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this));
175   url_fetcher->SetAutomaticallyRetryOn5xx(false);
176   const std::string auth_header("Authorization: Bearer " + access_token);
177   url_fetcher->AddExtraRequestHeader(auth_header);
178   url_fetcher->SetRequestContext(url_request_context_getter_.get());
179   url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
180                             net::LOAD_DO_NOT_SEND_COOKIES |
181                             net::LOAD_DISABLE_CACHE);
182   // TODO(maniscalco): Set an appropriate headers (User-Agent, what else?) on
183   // the request (bug 371521).
184   return url_fetcher.Pass();
185 }
186
187 void AttachmentDownloaderImpl::RequestAccessToken(
188     DownloadState* download_state) {
189   requests_waiting_for_access_token_.push_back(download_state);
190   // Start access token request if there is no active one.
191   if (access_token_request_ == NULL) {
192     access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart(
193         token_service_provider_.get(), account_id_, oauth2_scopes_, this);
194   }
195 }
196
197 void AttachmentDownloaderImpl::ReportResult(
198     const DownloadState& download_state,
199     const DownloadResult& result,
200     const scoped_refptr<base::RefCountedString>& attachment_data) {
201   std::vector<DownloadCallback>::const_iterator iter;
202   for (iter = download_state.user_callbacks.begin();
203        iter != download_state.user_callbacks.end();
204        ++iter) {
205     scoped_ptr<Attachment> attachment;
206     if (result == DOWNLOAD_SUCCESS) {
207       attachment.reset(new Attachment(Attachment::CreateWithId(
208           download_state.attachment_id, attachment_data)));
209     }
210
211     base::MessageLoop::current()->PostTask(
212         FROM_HERE, base::Bind(*iter, result, base::Passed(&attachment)));
213   }
214 }
215
216 bool AttachmentDownloaderImpl::VerifyHashIfPresent(
217     const net::URLFetcher& fetcher,
218     const std::string& data) {
219   const net::HttpResponseHeaders* headers = fetcher.GetResponseHeaders();
220   if (!headers) {
221     // No headers?  It passes.
222     return true;
223   }
224
225   std::string value;
226   if (!ExtractCrc32c(*headers, &value)) {
227     // No crc32c?  It passes.
228     return true;
229   }
230
231   if (value ==
232       AttachmentUploaderImpl::ComputeCrc32cHash(data.data(), data.size())) {
233     return true;
234   } else {
235     return false;
236   }
237 }
238
239 bool AttachmentDownloaderImpl::ExtractCrc32c(
240     const net::HttpResponseHeaders& headers,
241     std::string* crc32c) {
242   DCHECK(crc32c);
243   std::string header_value;
244   void* iter = NULL;
245   // Iterate over all matching headers.
246   while (headers.EnumerateHeader(&iter, "x-goog-hash", &header_value)) {
247     // Because EnumerateHeader is smart about list values, header_value will
248     // either be empty or a single name=value pair.
249     net::HttpUtil::NameValuePairsIterator pair_iter(
250         header_value.begin(), header_value.end(), ',');
251     if (pair_iter.GetNext()) {
252       if (pair_iter.name() == "crc32c") {
253         *crc32c = pair_iter.value();
254         DCHECK(!pair_iter.GetNext());
255         return true;
256       }
257     }
258   }
259
260   return false;
261 }
262
263 }  // namespace syncer