Home | History | Annotate | Download | only in attachments
      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 
     16 namespace syncer {
     17 
     18 struct 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 
     32 AttachmentDownloaderImpl::DownloadState::DownloadState(
     33     const AttachmentId& attachment_id,
     34     const AttachmentUrl& attachment_url)
     35     : attachment_id(attachment_id), attachment_url(attachment_url) {
     36 }
     37 
     38 AttachmentDownloaderImpl::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 
     58 AttachmentDownloaderImpl::~AttachmentDownloaderImpl() {
     59 }
     60 
     61 void 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 
     83 void 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 
    104 void 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 
    125 void 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 
    163 scoped_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 
    180 void 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 
    190 void 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