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"
7 #include "base/base64.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/metrics/histogram.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "base/sys_byteorder.h"
14 #include "base/time/time.h"
15 #include "net/base/load_flags.h"
16 #include "net/http/http_response_headers.h"
17 #include "net/http/http_status_code.h"
18 #include "net/http/http_util.h"
19 #include "net/url_request/url_fetcher.h"
20 #include "net/url_request/url_request_status.h"
21 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
22 #include "sync/internal_api/public/attachments/attachment_util.h"
23 #include "sync/protocol/sync.pb.h"
28 struct AttachmentDownloaderImpl::DownloadState
{
30 DownloadState(const AttachmentId
& attachment_id
,
31 const AttachmentUrl
& attachment_url
);
33 AttachmentId attachment_id
;
34 AttachmentUrl attachment_url
;
35 // |access_token| needed to invalidate if downloading attachment fails with
37 std::string access_token
;
38 scoped_ptr
<net::URLFetcher
> url_fetcher
;
39 std::vector
<DownloadCallback
> user_callbacks
;
40 base::TimeTicks start_time
;
43 AttachmentDownloaderImpl::DownloadState::DownloadState(
44 const AttachmentId
& attachment_id
,
45 const AttachmentUrl
& attachment_url
)
46 : attachment_id(attachment_id
), attachment_url(attachment_url
) {
49 AttachmentDownloaderImpl::AttachmentDownloaderImpl(
50 const GURL
& sync_service_url
,
51 const scoped_refptr
<net::URLRequestContextGetter
>&
52 url_request_context_getter
,
53 const std::string
& account_id
,
54 const OAuth2TokenService::ScopeSet
& scopes
,
55 const scoped_refptr
<OAuth2TokenServiceRequest::TokenServiceProvider
>&
56 token_service_provider
,
57 const std::string
& store_birthday
,
59 : OAuth2TokenService::Consumer("attachment-downloader-impl"),
60 sync_service_url_(sync_service_url
),
61 url_request_context_getter_(url_request_context_getter
),
62 account_id_(account_id
),
63 oauth2_scopes_(scopes
),
64 token_service_provider_(token_service_provider
),
65 raw_store_birthday_(store_birthday
),
66 model_type_(model_type
) {
67 DCHECK(url_request_context_getter_
.get());
68 DCHECK(!account_id
.empty());
69 DCHECK(!scopes
.empty());
70 DCHECK(token_service_provider_
.get());
71 DCHECK(!raw_store_birthday_
.empty());
74 AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
77 void AttachmentDownloaderImpl::DownloadAttachment(
78 const AttachmentId
& attachment_id
,
79 const DownloadCallback
& callback
) {
80 DCHECK(CalledOnValidThread());
82 AttachmentUrl url
= AttachmentUploaderImpl::GetURLForAttachmentId(
83 sync_service_url_
, attachment_id
).spec();
85 StateMap::iterator iter
= state_map_
.find(url
);
86 if (iter
== state_map_
.end()) {
87 // There is no request started for this attachment id. Let's create
88 // DownloadState and request access token for it.
89 scoped_ptr
<DownloadState
> new_download_state(
90 new DownloadState(attachment_id
, url
));
91 iter
= state_map_
.add(url
, new_download_state
.Pass()).first
;
92 RequestAccessToken(iter
->second
);
94 DownloadState
* download_state
= iter
->second
;
95 DCHECK(download_state
->attachment_id
== attachment_id
);
96 download_state
->user_callbacks
.push_back(callback
);
99 void AttachmentDownloaderImpl::OnGetTokenSuccess(
100 const OAuth2TokenService::Request
* request
,
101 const std::string
& access_token
,
102 const base::Time
& expiration_time
) {
103 DCHECK(CalledOnValidThread());
104 DCHECK(request
== access_token_request_
.get());
105 access_token_request_
.reset();
106 StateList::const_iterator iter
;
107 // Start downloads for all download requests waiting for access token.
108 for (iter
= requests_waiting_for_access_token_
.begin();
109 iter
!= requests_waiting_for_access_token_
.end();
111 DownloadState
* download_state
= *iter
;
112 download_state
->access_token
= access_token
;
113 download_state
->url_fetcher
=
114 CreateFetcher(download_state
->attachment_url
, access_token
).Pass();
115 download_state
->start_time
= base::TimeTicks::Now();
116 download_state
->url_fetcher
->Start();
118 requests_waiting_for_access_token_
.clear();
121 void AttachmentDownloaderImpl::OnGetTokenFailure(
122 const OAuth2TokenService::Request
* request
,
123 const GoogleServiceAuthError
& error
) {
124 DCHECK(CalledOnValidThread());
125 DCHECK(request
== access_token_request_
.get());
126 access_token_request_
.reset();
127 StateList::const_iterator iter
;
128 // Without access token all downloads fail.
129 for (iter
= requests_waiting_for_access_token_
.begin();
130 iter
!= requests_waiting_for_access_token_
.end();
132 DownloadState
* download_state
= *iter
;
133 scoped_refptr
<base::RefCountedString
> null_attachment_data
;
134 ReportResult(*download_state
, DOWNLOAD_TRANSIENT_ERROR
,
135 null_attachment_data
);
136 DCHECK(state_map_
.find(download_state
->attachment_url
) != state_map_
.end());
137 state_map_
.erase(download_state
->attachment_url
);
139 requests_waiting_for_access_token_
.clear();
142 void AttachmentDownloaderImpl::OnURLFetchComplete(
143 const net::URLFetcher
* source
) {
144 DCHECK(CalledOnValidThread());
146 // Find DownloadState by url.
147 AttachmentUrl url
= source
->GetOriginalURL().spec();
148 StateMap::iterator iter
= state_map_
.find(url
);
149 DCHECK(iter
!= state_map_
.end());
150 const DownloadState
& download_state
= *iter
->second
;
151 DCHECK(source
== download_state
.url_fetcher
.get());
153 DownloadResult result
= DOWNLOAD_TRANSIENT_ERROR
;
154 scoped_refptr
<base::RefCountedString
> attachment_data
;
155 uint32_t attachment_crc32c
= 0;
157 net::URLRequestStatus status
= source
->GetStatus();
158 const int response_code
= source
->GetResponseCode();
159 UMA_HISTOGRAM_SPARSE_SLOWLY("Sync.Attachments.DownloadResponseCode",
160 status
.is_success() ? response_code
: status
.error());
161 if (response_code
== net::HTTP_OK
) {
162 std::string data_as_string
;
163 source
->GetResponseAsString(&data_as_string
);
164 attachment_data
= base::RefCountedString::TakeString(&data_as_string
);
166 UMA_HISTOGRAM_LONG_TIMES("Sync.Attachments.DownloadTotalTime",
167 base::TimeTicks::Now() - download_state
.start_time
);
169 attachment_crc32c
= ComputeCrc32c(attachment_data
);
170 uint32_t crc32c_from_headers
= 0;
171 if (ExtractCrc32c(source
->GetResponseHeaders(), &crc32c_from_headers
) &&
172 attachment_crc32c
!= crc32c_from_headers
) {
173 // Fail download only if there is useful crc32c in header and it doesn't
174 // match data. All other cases are fine. When crc32c is not in headers
175 // locally calculated one will be stored and used for further checks.
176 result
= DOWNLOAD_TRANSIENT_ERROR
;
178 // If the id's crc32c doesn't match that of the downloaded attachment,
179 // then we're stuck and retrying is unlikely to help.
180 if (attachment_crc32c
!= download_state
.attachment_id
.GetCrc32c()) {
181 result
= DOWNLOAD_UNSPECIFIED_ERROR
;
183 result
= DOWNLOAD_SUCCESS
;
186 UMA_HISTOGRAM_BOOLEAN("Sync.Attachments.DownloadChecksumResult",
187 result
== DOWNLOAD_SUCCESS
);
188 } else if (response_code
== net::HTTP_UNAUTHORIZED
) {
189 // Server tells us we've got a bad token so invalidate it.
190 OAuth2TokenServiceRequest::InvalidateToken(token_service_provider_
.get(),
193 download_state
.access_token
);
194 // Fail the request, but indicate that it may be successful if retried.
195 result
= DOWNLOAD_TRANSIENT_ERROR
;
196 } else if (response_code
== net::HTTP_FORBIDDEN
) {
197 // User is not allowed to use attachments. Retrying won't help.
198 result
= DOWNLOAD_UNSPECIFIED_ERROR
;
199 } else if (response_code
== net::URLFetcher::RESPONSE_CODE_INVALID
) {
200 result
= DOWNLOAD_TRANSIENT_ERROR
;
202 ReportResult(download_state
, result
, attachment_data
);
203 state_map_
.erase(iter
);
206 scoped_ptr
<net::URLFetcher
> AttachmentDownloaderImpl::CreateFetcher(
207 const AttachmentUrl
& url
,
208 const std::string
& access_token
) {
209 scoped_ptr
<net::URLFetcher
> url_fetcher(
210 net::URLFetcher::Create(GURL(url
), net::URLFetcher::GET
, this));
211 AttachmentUploaderImpl::ConfigureURLFetcherCommon(
212 url_fetcher
.get(), access_token
, raw_store_birthday_
, model_type_
,
213 url_request_context_getter_
.get());
214 return url_fetcher
.Pass();
217 void AttachmentDownloaderImpl::RequestAccessToken(
218 DownloadState
* download_state
) {
219 requests_waiting_for_access_token_
.push_back(download_state
);
220 // Start access token request if there is no active one.
221 if (access_token_request_
== NULL
) {
222 access_token_request_
= OAuth2TokenServiceRequest::CreateAndStart(
223 token_service_provider_
.get(), account_id_
, oauth2_scopes_
, this);
227 void AttachmentDownloaderImpl::ReportResult(
228 const DownloadState
& download_state
,
229 const DownloadResult
& result
,
230 const scoped_refptr
<base::RefCountedString
>& attachment_data
) {
231 std::vector
<DownloadCallback
>::const_iterator iter
;
232 for (iter
= download_state
.user_callbacks
.begin();
233 iter
!= download_state
.user_callbacks
.end();
235 scoped_ptr
<Attachment
> attachment
;
236 if (result
== DOWNLOAD_SUCCESS
) {
237 attachment
.reset(new Attachment(Attachment::CreateFromParts(
238 download_state
.attachment_id
, attachment_data
)));
241 base::MessageLoop::current()->PostTask(
242 FROM_HERE
, base::Bind(*iter
, result
, base::Passed(&attachment
)));
246 bool AttachmentDownloaderImpl::ExtractCrc32c(
247 const net::HttpResponseHeaders
* headers
,
254 std::string crc32c_encoded
;
255 std::string header_value
;
257 // Iterate over all matching headers.
258 while (headers
->EnumerateHeader(&iter
, "x-goog-hash", &header_value
)) {
259 // Because EnumerateHeader is smart about list values, header_value will
260 // either be empty or a single name=value pair.
261 net::HttpUtil::NameValuePairsIterator
pair_iter(
262 header_value
.begin(), header_value
.end(), ',');
263 if (pair_iter
.GetNext()) {
264 if (pair_iter
.name() == "crc32c") {
265 crc32c_encoded
= pair_iter
.value();
266 DCHECK(!pair_iter
.GetNext());
271 // Check if header was found
272 if (crc32c_encoded
.empty())
274 std::string crc32c_raw
;
275 if (!base::Base64Decode(crc32c_encoded
, &crc32c_raw
))
278 if (crc32c_raw
.size() != sizeof(*crc32c
))
282 base::NetToHost32(*reinterpret_cast<const uint32_t*>(crc32c_raw
.c_str()));
286 } // namespace syncer