1 // Copyright 2017 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 "components/download/internal/background_service/in_memory_download.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/bind.h"
11 #include "components/download/internal/background_service/blob_task_proxy.h"
12 #include "net/base/load_flags.h"
13 #include "net/traffic_annotation/network_traffic_annotation.h"
14 #include "services/network/public/mojom/url_response_head.mojom.h"
15 #include "storage/browser/blob/blob_data_handle.h"
16 #include "storage/browser/blob/blob_storage_context.h"
17 
18 namespace download {
19 
InMemoryDownload(const std::string & guid)20 InMemoryDownload::InMemoryDownload(const std::string& guid)
21     : guid_(guid),
22       state_(State::INITIAL),
23       paused_(false),
24       bytes_downloaded_(0u),
25       bytes_uploaded_(0u) {}
26 
27 InMemoryDownload::~InMemoryDownload() = default;
28 
InMemoryDownloadImpl(const std::string & guid,const RequestParams & request_params,scoped_refptr<network::ResourceRequestBody> request_body,const net::NetworkTrafficAnnotationTag & traffic_annotation,Delegate * delegate,network::mojom::URLLoaderFactory * url_loader_factory,scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)29 InMemoryDownloadImpl::InMemoryDownloadImpl(
30     const std::string& guid,
31     const RequestParams& request_params,
32     scoped_refptr<network::ResourceRequestBody> request_body,
33     const net::NetworkTrafficAnnotationTag& traffic_annotation,
34     Delegate* delegate,
35     network::mojom::URLLoaderFactory* url_loader_factory,
36     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
37     : InMemoryDownload(guid),
38       request_params_(request_params),
39       request_body_(std::move(request_body)),
40       traffic_annotation_(traffic_annotation),
41       url_loader_factory_(url_loader_factory),
42       io_task_runner_(io_task_runner),
43       delegate_(delegate),
44       completion_notified_(false),
45       started_(false) {
46   DCHECK(!guid_.empty());
47   DCHECK(delegate_);
48 }
49 
~InMemoryDownloadImpl()50 InMemoryDownloadImpl::~InMemoryDownloadImpl() {
51   io_task_runner_->DeleteSoon(FROM_HERE, blob_task_proxy_.release());
52 }
53 
Start()54 void InMemoryDownloadImpl::Start() {
55   DCHECK(state_ == State::INITIAL) << "Only call Start() for new download.";
56   state_ = State::RETRIEVE_BLOB_CONTEXT;
57   delegate_->RetrieveBlobContextGetter(
58       base::BindOnce(&InMemoryDownloadImpl::OnRetrievedBlobContextGetter,
59                      weak_ptr_factory_.GetWeakPtr()));
60 }
61 
OnRetrievedBlobContextGetter(BlobContextGetter blob_context_getter)62 void InMemoryDownloadImpl::OnRetrievedBlobContextGetter(
63     BlobContextGetter blob_context_getter) {
64   DCHECK(state_ == State::RETRIEVE_BLOB_CONTEXT);
65   blob_task_proxy_ =
66       BlobTaskProxy::Create(blob_context_getter, io_task_runner_);
67   SendRequest();
68   state_ = State::IN_PROGRESS;
69 }
70 
Pause()71 void InMemoryDownloadImpl::Pause() {
72   if (state_ == State::IN_PROGRESS)
73     paused_ = true;
74 }
75 
Resume()76 void InMemoryDownloadImpl::Resume() {
77   paused_ = false;
78 
79   switch (state_) {
80     case State::INITIAL:
81     case State::RETRIEVE_BLOB_CONTEXT:
82       return;
83     case State::IN_PROGRESS:
84       // Let the network pipe continue to read data.
85       if (resume_callback_)
86         std::move(resume_callback_).Run();
87       return;
88     case State::FAILED:
89       // Restart the download.
90       Reset();
91       SendRequest();
92       state_ = State::IN_PROGRESS;
93       return;
94     case State::COMPLETE:
95       NotifyDelegateDownloadComplete();
96       return;
97   }
98 }
99 
ResultAsBlob() const100 std::unique_ptr<storage::BlobDataHandle> InMemoryDownloadImpl::ResultAsBlob()
101     const {
102   DCHECK(state_ == State::COMPLETE || state_ == State::FAILED);
103   // Return a copy.
104   return std::make_unique<storage::BlobDataHandle>(*blob_data_handle_);
105 }
106 
EstimateMemoryUsage() const107 size_t InMemoryDownloadImpl::EstimateMemoryUsage() const {
108   return bytes_downloaded_;
109 }
110 
OnDataReceived(base::StringPiece string_piece,base::OnceClosure resume)111 void InMemoryDownloadImpl::OnDataReceived(base::StringPiece string_piece,
112                                           base::OnceClosure resume) {
113   size_t size = string_piece.as_string().size();
114   data_.append(string_piece.as_string().data(), size);
115   bytes_downloaded_ += size;
116 
117   if (paused_) {
118     // Read data later and cache the resumption callback when paused.
119     resume_callback_ = std::move(resume);
120     return;
121   }
122 
123   // Continue to read data.
124   std::move(resume).Run();
125 
126   // TODO(xingliu): Throttle the update frequency. See https://crbug.com/809674.
127   delegate_->OnDownloadProgress(this);
128 }
129 
OnComplete(bool success)130 void InMemoryDownloadImpl::OnComplete(bool success) {
131   if (success) {
132     SaveAsBlob();
133     return;
134   }
135 
136   state_ = State::FAILED;
137 
138   // Release download data.
139   data_.clear();
140 
141   // OnComplete() called without OnResponseStarted(). This will happen when the
142   // request was aborted.
143   if (!started_)
144     OnResponseStarted(GURL(), network::mojom::URLResponseHead());
145 
146   NotifyDelegateDownloadComplete();
147 }
148 
OnRetry(base::OnceClosure start_retry)149 void InMemoryDownloadImpl::OnRetry(base::OnceClosure start_retry) {
150   Reset();
151 
152   // The original URL is recorded in this class instead of |loader_|, so when
153   // running retry closure from SimpleUrlLoader, add back the original URL.
154   url_chain_.push_back(request_params_.url);
155 
156   std::move(start_retry).Run();
157 }
158 
SaveAsBlob()159 void InMemoryDownloadImpl::SaveAsBlob() {
160   auto callback = base::BindOnce(&InMemoryDownloadImpl::OnSaveBlobDone,
161                                  weak_ptr_factory_.GetWeakPtr());
162   auto data = std::make_unique<std::string>(std::move(data_));
163   blob_task_proxy_->SaveAsBlob(std::move(data), std::move(callback));
164 }
165 
OnSaveBlobDone(std::unique_ptr<storage::BlobDataHandle> blob_handle,storage::BlobStatus status)166 void InMemoryDownloadImpl::OnSaveBlobDone(
167     std::unique_ptr<storage::BlobDataHandle> blob_handle,
168     storage::BlobStatus status) {
169   // |status| is valid on IO thread, consumer of |blob_handle| should validate
170   // the data when using the blob data.
171   state_ =
172       (status == storage::BlobStatus::DONE) ? State::COMPLETE : State::FAILED;
173 
174   // TODO(xingliu): Add metric for blob status code. If failed, consider remove
175   // |blob_data_handle_|. See https://crbug.com/809674.
176   DCHECK(data_.empty())
177       << "Download data should be contained in |blob_data_handle_|.";
178   blob_data_handle_ = std::move(blob_handle);
179   completion_time_ = base::Time::Now();
180 
181   // Resets network backend.
182   loader_.reset();
183 
184   // Not considering |paused_| here, if pause after starting a blob operation,
185   // just let it finish.
186   NotifyDelegateDownloadComplete();
187 }
188 
NotifyDelegateDownloadComplete()189 void InMemoryDownloadImpl::NotifyDelegateDownloadComplete() {
190   if (completion_notified_)
191     return;
192   completion_notified_ = true;
193 
194   delegate_->OnDownloadComplete(this);
195 }
196 
SendRequest()197 void InMemoryDownloadImpl::SendRequest() {
198   auto request = std::make_unique<network::ResourceRequest>();
199   request->url = request_params_.url;
200   request->method = request_params_.method;
201   request->headers = request_params_.request_headers;
202   request->load_flags = net::LOAD_DISABLE_CACHE;
203   if (request_body_) {
204     request->request_body = std::move(request_body_);
205     request->enable_upload_progress = true;
206   }
207 
208   url_chain_.push_back(request_params_.url);
209 
210   loader_ =
211       network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
212   loader_->SetOnRedirectCallback(base::BindRepeating(
213       &InMemoryDownloadImpl::OnRedirect, weak_ptr_factory_.GetWeakPtr()));
214   loader_->SetOnResponseStartedCallback(
215       base::BindRepeating(&InMemoryDownloadImpl::OnResponseStarted,
216                           weak_ptr_factory_.GetWeakPtr()));
217   loader_->SetOnUploadProgressCallback(base::BindRepeating(
218       &InMemoryDownloadImpl::OnUploadProgress, weak_ptr_factory_.GetWeakPtr()));
219 
220   // TODO(xingliu): Use SimpleURLLoader's retry when it won't hit CHECK in
221   // SharedURLLoaderFactory.
222   loader_->DownloadAsStream(url_loader_factory_, this);
223 }
224 
OnRedirect(const net::RedirectInfo & redirect_info,const network::mojom::URLResponseHead & response_head,std::vector<std::string> * to_be_removed_headers)225 void InMemoryDownloadImpl::OnRedirect(
226     const net::RedirectInfo& redirect_info,
227     const network::mojom::URLResponseHead& response_head,
228     std::vector<std::string>* to_be_removed_headers) {
229   url_chain_.push_back(redirect_info.new_url);
230 }
231 
OnResponseStarted(const GURL & final_url,const network::mojom::URLResponseHead & response_head)232 void InMemoryDownloadImpl::OnResponseStarted(
233     const GURL& final_url,
234     const network::mojom::URLResponseHead& response_head) {
235   started_ = true;
236   response_headers_ = response_head.headers;
237 
238   delegate_->OnDownloadStarted(this);
239 }
240 
OnUploadProgress(uint64_t position,uint64_t total)241 void InMemoryDownloadImpl::OnUploadProgress(uint64_t position, uint64_t total) {
242   bytes_uploaded_ = position;
243   delegate_->OnUploadProgress(this);
244 }
245 
Reset()246 void InMemoryDownloadImpl::Reset() {
247   data_.clear();
248   url_chain_.clear();
249   response_headers_.reset();
250   bytes_downloaded_ = 0u;
251   completion_notified_ = false;
252   started_ = false;
253   resume_callback_.Reset();
254 }
255 
256 }  // namespace download
257