Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / sync / internal_api / attachments / attachment_downloader_impl.cc
blob2915296d9f1bf88fca9d8c13ceffa83594bbaf2a
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"
8 #include "base/bind.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"
24 #include "url/gurl.h"
26 namespace syncer {
28 struct AttachmentDownloaderImpl::DownloadState {
29 public:
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
36 // HTTP_UNAUTHORIZED.
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,
58 ModelType model_type)
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();
110 ++iter) {
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();
131 ++iter) {
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;
177 } else {
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;
182 } else {
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(),
191 account_id_,
192 oauth2_scopes_,
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();
234 ++iter) {
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,
248 uint32_t* crc32c) {
249 DCHECK(crc32c);
250 if (!headers) {
251 return false;
254 std::string crc32c_encoded;
255 std::string header_value;
256 void* iter = NULL;
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());
267 break;
271 // Check if header was found
272 if (crc32c_encoded.empty())
273 return false;
274 std::string crc32c_raw;
275 if (!base::Base64Decode(crc32c_encoded, &crc32c_raw))
276 return false;
278 if (crc32c_raw.size() != sizeof(*crc32c))
279 return false;
281 *crc32c =
282 base::NetToHost32(*reinterpret_cast<const uint32_t*>(crc32c_raw.c_str()));
283 return true;
286 } // namespace syncer