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_downloader_impl.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"
20 struct AttachmentDownloaderImpl::DownloadState {
22 DownloadState(const AttachmentId& attachment_id,
23 const AttachmentUrl& attachment_url);
25 AttachmentId attachment_id;
26 AttachmentUrl attachment_url;
27 // |access_token| needed to invalidate if downloading attachment fails with
29 std::string access_token;
30 scoped_ptr<net::URLFetcher> url_fetcher;
31 std::vector<DownloadCallback> user_callbacks;
34 AttachmentDownloaderImpl::DownloadState::DownloadState(
35 const AttachmentId& attachment_id,
36 const AttachmentUrl& attachment_url)
37 : attachment_id(attachment_id), attachment_url(attachment_url) {
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());
60 AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
63 void AttachmentDownloaderImpl::DownloadAttachment(
64 const AttachmentId& attachment_id,
65 const DownloadCallback& callback) {
66 DCHECK(CalledOnValidThread());
68 AttachmentUrl url = AttachmentUploaderImpl::GetURLForAttachmentId(
69 sync_service_url_, attachment_id).spec();
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);
80 DownloadState* download_state = iter->second;
81 DCHECK(download_state->attachment_id == attachment_id);
82 download_state->user_callbacks.push_back(callback);
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();
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();
103 requests_waiting_for_access_token_.clear();
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();
117 DownloadState* download_state = *iter;
118 scoped_refptr<base::RefCountedString> null_attachment_data;
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);
124 requests_waiting_for_access_token_.clear();
127 void AttachmentDownloaderImpl::OnURLFetchComplete(
128 const net::URLFetcher* source) {
129 DCHECK(CalledOnValidThread());
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());
138 DownloadResult result = DOWNLOAD_TRANSIENT_ERROR;
139 scoped_refptr<base::RefCountedString> attachment_data;
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);
149 // TODO(maniscalco): Test me!
150 result = DOWNLOAD_TRANSIENT_ERROR;
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(),
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;
166 ReportResult(download_state, result, attachment_data);
167 state_map_.erase(iter);
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();
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);
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();
205 scoped_ptr<Attachment> attachment;
206 if (result == DOWNLOAD_SUCCESS) {
207 attachment.reset(new Attachment(Attachment::CreateWithId(
208 download_state.attachment_id, attachment_data)));
211 base::MessageLoop::current()->PostTask(
212 FROM_HERE, base::Bind(*iter, result, base::Passed(&attachment)));
216 bool AttachmentDownloaderImpl::VerifyHashIfPresent(
217 const net::URLFetcher& fetcher,
218 const std::string& data) {
219 const net::HttpResponseHeaders* headers = fetcher.GetResponseHeaders();
221 // No headers? It passes.
226 if (!ExtractCrc32c(*headers, &value)) {
227 // No crc32c? It passes.
232 AttachmentUploaderImpl::ComputeCrc32cHash(data.data(), data.size())) {
239 bool AttachmentDownloaderImpl::ExtractCrc32c(
240 const net::HttpResponseHeaders& headers,
241 std::string* crc32c) {
243 std::string header_value;
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());
263 } // namespace syncer