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_status_code.h"
11#include "net/url_request/url_fetcher.h"
12#include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
13#include "sync/protocol/sync.pb.h"
14#include "url/gurl.h"
15
16namespace syncer {
17
18struct AttachmentDownloaderImpl::DownloadState {
19 public:
20  DownloadState(const AttachmentId& attachment_id,
21                const AttachmentUrl& attachment_url);
22
23  AttachmentId attachment_id;
24  AttachmentUrl attachment_url;
25  // |access_token| needed to invalidate if downloading attachment fails with
26  // HTTP_UNAUTHORIZED.
27  std::string access_token;
28  scoped_ptr<net::URLFetcher> url_fetcher;
29  std::vector<DownloadCallback> user_callbacks;
30};
31
32AttachmentDownloaderImpl::DownloadState::DownloadState(
33    const AttachmentId& attachment_id,
34    const AttachmentUrl& attachment_url)
35    : attachment_id(attachment_id), attachment_url(attachment_url) {
36}
37
38AttachmentDownloaderImpl::AttachmentDownloaderImpl(
39    const GURL& sync_service_url,
40    const scoped_refptr<net::URLRequestContextGetter>&
41        url_request_context_getter,
42    const std::string& account_id,
43    const OAuth2TokenService::ScopeSet& scopes,
44    const scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>&
45        token_service_provider)
46    : OAuth2TokenService::Consumer("attachment-downloader-impl"),
47      sync_service_url_(sync_service_url),
48      url_request_context_getter_(url_request_context_getter),
49      account_id_(account_id),
50      oauth2_scopes_(scopes),
51      token_service_provider_(token_service_provider) {
52  DCHECK(!account_id.empty());
53  DCHECK(!scopes.empty());
54  DCHECK(token_service_provider_.get());
55  DCHECK(url_request_context_getter_.get());
56}
57
58AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
59}
60
61void AttachmentDownloaderImpl::DownloadAttachment(
62    const AttachmentId& attachment_id,
63    const DownloadCallback& callback) {
64  DCHECK(CalledOnValidThread());
65
66  AttachmentUrl url = AttachmentUploaderImpl::GetURLForAttachmentId(
67                          sync_service_url_, attachment_id).spec();
68
69  StateMap::iterator iter = state_map_.find(url);
70  if (iter == state_map_.end()) {
71    // There is no request started for this attachment id. Let's create
72    // DownloadState and request access token for it.
73    scoped_ptr<DownloadState> new_download_state(
74        new DownloadState(attachment_id, url));
75    iter = state_map_.add(url, new_download_state.Pass()).first;
76    RequestAccessToken(iter->second);
77  }
78  DownloadState* download_state = iter->second;
79  DCHECK(download_state->attachment_id == attachment_id);
80  download_state->user_callbacks.push_back(callback);
81}
82
83void AttachmentDownloaderImpl::OnGetTokenSuccess(
84    const OAuth2TokenService::Request* request,
85    const std::string& access_token,
86    const base::Time& expiration_time) {
87  DCHECK(CalledOnValidThread());
88  DCHECK(request == access_token_request_.get());
89  access_token_request_.reset();
90  StateList::const_iterator iter;
91  // Start downloads for all download requests waiting for access token.
92  for (iter = requests_waiting_for_access_token_.begin();
93       iter != requests_waiting_for_access_token_.end();
94       ++iter) {
95    DownloadState* download_state = *iter;
96    download_state->access_token = access_token;
97    download_state->url_fetcher =
98        CreateFetcher(download_state->attachment_url, access_token).Pass();
99    download_state->url_fetcher->Start();
100  }
101  requests_waiting_for_access_token_.clear();
102}
103
104void AttachmentDownloaderImpl::OnGetTokenFailure(
105    const OAuth2TokenService::Request* request,
106    const GoogleServiceAuthError& error) {
107  DCHECK(CalledOnValidThread());
108  DCHECK(request == access_token_request_.get());
109  access_token_request_.reset();
110  StateList::const_iterator iter;
111  // Without access token all downloads fail.
112  for (iter = requests_waiting_for_access_token_.begin();
113       iter != requests_waiting_for_access_token_.end();
114       ++iter) {
115    DownloadState* download_state = *iter;
116    scoped_refptr<base::RefCountedString> null_attachment_data;
117    ReportResult(
118        *download_state, DOWNLOAD_TRANSIENT_ERROR, null_attachment_data);
119    DCHECK(state_map_.find(download_state->attachment_url) != state_map_.end());
120    state_map_.erase(download_state->attachment_url);
121  }
122  requests_waiting_for_access_token_.clear();
123}
124
125void AttachmentDownloaderImpl::OnURLFetchComplete(
126    const net::URLFetcher* source) {
127  DCHECK(CalledOnValidThread());
128
129  // Find DownloadState by url.
130  AttachmentUrl url = source->GetOriginalURL().spec();
131  StateMap::iterator iter = state_map_.find(url);
132  DCHECK(iter != state_map_.end());
133  const DownloadState& download_state = *iter->second;
134  DCHECK(source == download_state.url_fetcher.get());
135
136  DownloadResult result = DOWNLOAD_TRANSIENT_ERROR;
137  scoped_refptr<base::RefCountedString> attachment_data;
138
139  const int response_code = source->GetResponseCode();
140  if (response_code == net::HTTP_OK) {
141    result = DOWNLOAD_SUCCESS;
142    std::string data_as_string;
143    source->GetResponseAsString(&data_as_string);
144    attachment_data = base::RefCountedString::TakeString(&data_as_string);
145  } else if (response_code == net::HTTP_UNAUTHORIZED) {
146    // Server tells us we've got a bad token so invalidate it.
147    OAuth2TokenServiceRequest::InvalidateToken(token_service_provider_.get(),
148                                               account_id_,
149                                               oauth2_scopes_,
150                                               download_state.access_token);
151    // Fail the request, but indicate that it may be successful if retried.
152    result = DOWNLOAD_TRANSIENT_ERROR;
153  } else if (response_code == net::HTTP_FORBIDDEN) {
154    // User is not allowed to use attachments.  Retrying won't help.
155    result = DOWNLOAD_UNSPECIFIED_ERROR;
156  } else if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) {
157    result = DOWNLOAD_TRANSIENT_ERROR;
158  }
159  ReportResult(download_state, result, attachment_data);
160  state_map_.erase(iter);
161}
162
163scoped_ptr<net::URLFetcher> AttachmentDownloaderImpl::CreateFetcher(
164    const AttachmentUrl& url,
165    const std::string& access_token) {
166  scoped_ptr<net::URLFetcher> url_fetcher(
167      net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this));
168  url_fetcher->SetAutomaticallyRetryOn5xx(false);
169  const std::string auth_header("Authorization: Bearer " + access_token);
170  url_fetcher->AddExtraRequestHeader(auth_header);
171  url_fetcher->SetRequestContext(url_request_context_getter_.get());
172  url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
173                            net::LOAD_DO_NOT_SEND_COOKIES |
174                            net::LOAD_DISABLE_CACHE);
175  // TODO(maniscalco): Set an appropriate headers (User-Agent, what else?) on
176  // the request (bug 371521).
177  return url_fetcher.Pass();
178}
179
180void AttachmentDownloaderImpl::RequestAccessToken(
181    DownloadState* download_state) {
182  requests_waiting_for_access_token_.push_back(download_state);
183  // Start access token request if there is no active one.
184  if (access_token_request_ == NULL) {
185    access_token_request_ = OAuth2TokenServiceRequest::CreateAndStart(
186        token_service_provider_.get(), account_id_, oauth2_scopes_, this);
187  }
188}
189
190void AttachmentDownloaderImpl::ReportResult(
191    const DownloadState& download_state,
192    const DownloadResult& result,
193    const scoped_refptr<base::RefCountedString>& attachment_data) {
194  std::vector<DownloadCallback>::const_iterator iter;
195  for (iter = download_state.user_callbacks.begin();
196       iter != download_state.user_callbacks.end();
197       ++iter) {
198    scoped_ptr<Attachment> attachment;
199    if (result == DOWNLOAD_SUCCESS) {
200      attachment.reset(new Attachment(Attachment::CreateWithId(
201          download_state.attachment_id, attachment_data)));
202    }
203
204    base::MessageLoop::current()->PostTask(
205        FROM_HERE, base::Bind(*iter, result, base::Passed(&attachment)));
206  }
207}
208
209}  // namespace syncer
210