1 // Copyright 2019 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 "content/browser/web_package/web_bundle_reader.h"
6 
7 #include <limits>
8 
9 #include "base/logging.h"
10 #include "base/numerics/safe_math.h"
11 #include "base/task/post_task.h"
12 #include "base/task/task_traits.h"
13 #include "base/task/thread_pool.h"
14 #include "content/browser/web_package/web_bundle_blob_data_source.h"
15 #include "content/browser/web_package/web_bundle_source.h"
16 #include "content/public/browser/browser_task_traits.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "mojo/public/cpp/bindings/pending_remote.h"
19 #include "mojo/public/cpp/system/data_pipe_producer.h"
20 #include "mojo/public/cpp/system/file_data_source.h"
21 #include "mojo/public/cpp/system/platform_handle.h"
22 #include "net/base/url_util.h"
23 #include "third_party/blink/public/common/web_package/web_package_request_matcher.h"
24 
25 namespace content {
26 
SharedFile(std::unique_ptr<WebBundleSource> source)27 WebBundleReader::SharedFile::SharedFile(
28     std::unique_ptr<WebBundleSource> source) {
29   base::ThreadPool::PostTaskAndReplyWithResult(
30       FROM_HERE, {base::MayBlock()},
31       base::BindOnce(
32           [](std::unique_ptr<WebBundleSource> source)
33               -> std::unique_ptr<base::File> { return source->OpenFile(); },
34           std::move(source)),
35       base::BindOnce(&SharedFile::SetFile, base::RetainedRef(this)));
36 }
37 
DuplicateFile(base::OnceCallback<void (base::File)> callback)38 void WebBundleReader::SharedFile::DuplicateFile(
39     base::OnceCallback<void(base::File)> callback) {
40   // Basically this interface expects this method is called at most once. Have
41   // a DCHECK for the case that does not work for a clear reason, just in case.
42   // The call site also have another DCHECK for external callers not to cause
43   // such problematic cases.
44   DCHECK(duplicate_callback_.is_null());
45   duplicate_callback_ = std::move(callback);
46 
47   if (file_)
48     SetFile(std::move(file_));
49 }
50 
operator ->()51 base::File* WebBundleReader::SharedFile::operator->() {
52   DCHECK(file_);
53   return file_.get();
54 }
55 
~SharedFile()56 WebBundleReader::SharedFile::~SharedFile() {
57   // Move the last reference to |file_| that leads an internal blocking call
58   // that is not permitted here.
59   base::ThreadPool::PostTask(
60       FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
61       base::BindOnce([](std::unique_ptr<base::File> file) {},
62                      std::move(file_)));
63 }
64 
SetFile(std::unique_ptr<base::File> file)65 void WebBundleReader::SharedFile::SetFile(std::unique_ptr<base::File> file) {
66   file_ = std::move(file);
67 
68   if (duplicate_callback_.is_null())
69     return;
70 
71   base::ThreadPool::PostTaskAndReplyWithResult(
72       FROM_HERE, {base::MayBlock()},
73       base::BindOnce(
74           [](base::File* file) -> base::File { return file->Duplicate(); },
75           file_.get()),
76       std::move(duplicate_callback_));
77 }
78 
79 class WebBundleReader::SharedFileDataSource final
80     : public mojo::DataPipeProducer::DataSource {
81  public:
SharedFileDataSource(scoped_refptr<WebBundleReader::SharedFile> file,uint64_t offset,uint64_t length)82   SharedFileDataSource(scoped_refptr<WebBundleReader::SharedFile> file,
83                        uint64_t offset,
84                        uint64_t length)
85       : file_(std::move(file)), offset_(offset), length_(length) {
86     error_ = mojo::FileDataSource::ConvertFileErrorToMojoResult(
87         (*file_)->error_details());
88 
89     // base::File::Read takes int64_t as an offset. So, offset + length should
90     // not overflow in int64_t.
91     uint64_t max_offset;
92     if (!base::CheckAdd(offset, length).AssignIfValid(&max_offset) ||
93         (std::numeric_limits<int64_t>::max() < max_offset)) {
94       error_ = MOJO_RESULT_INVALID_ARGUMENT;
95     }
96   }
97 
98  private:
99   // Implements mojo::DataPipeProducer::DataSource. Following methods are called
100   // on a blockable sequenced task runner.
GetLength() const101   uint64_t GetLength() const override { return length_; }
Read(uint64_t offset,base::span<char> buffer)102   ReadResult Read(uint64_t offset, base::span<char> buffer) override {
103     ReadResult result;
104     result.result = error_;
105 
106     if (length_ < offset)
107       result.result = MOJO_RESULT_INVALID_ARGUMENT;
108 
109     if (result.result != MOJO_RESULT_OK)
110       return result;
111 
112     uint64_t readable_size = length_ - offset;
113     uint64_t writable_size = buffer.size();
114     uint64_t copyable_size =
115         std::min(std::min(readable_size, writable_size),
116                  static_cast<uint64_t>(std::numeric_limits<int>::max()));
117 
118     int bytes_read =
119         (*file_)->Read(offset_ + offset, buffer.data(), copyable_size);
120     if (bytes_read < 0) {
121       result.result = mojo::FileDataSource::ConvertFileErrorToMojoResult(
122           (*file_)->GetLastFileError());
123     } else {
124       result.bytes_read = bytes_read;
125     }
126     return result;
127   }
128 
129   scoped_refptr<WebBundleReader::SharedFile> file_;
130   MojoResult error_;
131   const uint64_t offset_;
132   const uint64_t length_;
133 
134   DISALLOW_COPY_AND_ASSIGN(SharedFileDataSource);
135 };
136 
WebBundleReader(std::unique_ptr<WebBundleSource> source)137 WebBundleReader::WebBundleReader(std::unique_ptr<WebBundleSource> source)
138     : source_(std::move(source)),
139       parser_(std::make_unique<data_decoder::SafeWebBundleParser>()),
140       file_(base::MakeRefCounted<SharedFile>(source_->Clone())) {
141   DCHECK(source_->is_trusted_file() || source_->is_file());
142 }
143 
WebBundleReader(std::unique_ptr<WebBundleSource> source,int64_t content_length,mojo::ScopedDataPipeConsumerHandle outer_response_body,network::mojom::URLLoaderClientEndpointsPtr endpoints,BrowserContext::BlobContextGetter blob_context_getter)144 WebBundleReader::WebBundleReader(
145     std::unique_ptr<WebBundleSource> source,
146     int64_t content_length,
147     mojo::ScopedDataPipeConsumerHandle outer_response_body,
148     network::mojom::URLLoaderClientEndpointsPtr endpoints,
149     BrowserContext::BlobContextGetter blob_context_getter)
150     : source_(std::move(source)),
151       parser_(std::make_unique<data_decoder::SafeWebBundleParser>()) {
152   DCHECK(source_->is_network());
153   mojo::PendingRemote<data_decoder::mojom::BundleDataSource> pending_remote;
154   blob_data_source_ = std::make_unique<WebBundleBlobDataSource>(
155       content_length, std::move(outer_response_body), std::move(endpoints),
156       std::move(blob_context_getter));
157   blob_data_source_->AddReceiver(
158       pending_remote.InitWithNewPipeAndPassReceiver());
159   parser_->OpenDataSource(std::move(pending_remote));
160 }
161 
~WebBundleReader()162 WebBundleReader::~WebBundleReader() {
163   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
164 }
165 
ReadMetadata(MetadataCallback callback)166 void WebBundleReader::ReadMetadata(MetadataCallback callback) {
167   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
168   DCHECK_EQ(state_, State::kInitial);
169 
170   if (!blob_data_source_) {
171     DCHECK(source_->is_trusted_file() || source_->is_file());
172     file_->DuplicateFile(base::BindOnce(&WebBundleReader::ReadMetadataInternal,
173                                         this, std::move(callback)));
174     return;
175   }
176   DCHECK(source_->is_network());
177   parser_->ParseMetadata(base::BindOnce(&WebBundleReader::OnMetadataParsed,
178                                         base::Unretained(this),
179                                         std::move(callback)));
180 }
181 
ReadResponse(const network::ResourceRequest & resource_request,const std::string & accept_langs,ResponseCallback callback)182 void WebBundleReader::ReadResponse(
183     const network::ResourceRequest& resource_request,
184     const std::string& accept_langs,
185     ResponseCallback callback) {
186   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
187   DCHECK_NE(state_, State::kInitial);
188 
189   auto it = entries_.find(net::SimplifyUrlForRequest(resource_request.url));
190   if (it == entries_.end() || it->second->response_locations.empty()) {
191     PostTask(
192         FROM_HERE,
193         base::BindOnce(
194             std::move(callback), nullptr,
195             data_decoder::mojom::BundleResponseParseError::New(
196                 data_decoder::mojom::BundleParseErrorType::kParserInternalError,
197                 "Not found in Web Bundle file.")));
198     return;
199   }
200   const data_decoder::mojom::BundleIndexValuePtr& entry = it->second;
201 
202   size_t response_index = 0;
203   if (!entry->variants_value.empty()) {
204     // Select the best variant for the request.
205     blink::WebPackageRequestMatcher matcher(resource_request.headers,
206                                             accept_langs);
207     auto found = matcher.FindBestMatchingIndex(entry->variants_value);
208     if (!found || *found >= entry->response_locations.size()) {
209       PostTask(
210           FROM_HERE,
211           base::BindOnce(
212               std::move(callback), nullptr,
213               data_decoder::mojom::BundleResponseParseError::New(
214                   data_decoder::mojom::BundleParseErrorType::
215                       kParserInternalError,
216                   "Cannot find a response that matches request headers.")));
217       return;
218     }
219     response_index = *found;
220   }
221   auto response_location = entry->response_locations[response_index].Clone();
222 
223   if (state_ == State::kDisconnected) {
224     // Try reconnecting, if not attempted yet.
225     if (pending_read_responses_.empty())
226       Reconnect();
227     pending_read_responses_.emplace_back(std::move(response_location),
228                                          std::move(callback));
229     return;
230   }
231 
232   ReadResponseInternal(std::move(response_location), std::move(callback));
233 }
234 
ReadResponseInternal(data_decoder::mojom::BundleResponseLocationPtr location,ResponseCallback callback)235 void WebBundleReader::ReadResponseInternal(
236     data_decoder::mojom::BundleResponseLocationPtr location,
237     ResponseCallback callback) {
238   parser_->ParseResponse(
239       location->offset, location->length,
240       base::BindOnce(&WebBundleReader::OnResponseParsed, base::Unretained(this),
241                      std::move(callback)));
242 }
243 
Reconnect()244 void WebBundleReader::Reconnect() {
245   DCHECK(!parser_);
246   parser_ = std::make_unique<data_decoder::SafeWebBundleParser>();
247 
248   if (!blob_data_source_) {
249     DCHECK(source_->is_trusted_file() || source_->is_file());
250     file_->DuplicateFile(
251         base::BindOnce(&WebBundleReader::ReconnectForFile, this));
252     return;
253   }
254   DCHECK(source_->is_network());
255   mojo::PendingRemote<data_decoder::mojom::BundleDataSource> pending_remote;
256   blob_data_source_->AddReceiver(
257       pending_remote.InitWithNewPipeAndPassReceiver());
258   parser_->OpenDataSource(std::move(pending_remote));
259 
260   base::PostTask(FROM_HERE, {BrowserThread::UI},
261                  base::BindOnce(&WebBundleReader::DidReconnect, this,
262                                 base::nullopt /* error */));
263 }
264 
ReconnectForFile(base::File file)265 void WebBundleReader::ReconnectForFile(base::File file) {
266   base::File::Error file_error = parser_->OpenFile(std::move(file));
267   base::Optional<std::string> error;
268   if (file_error != base::File::FILE_OK)
269     error = base::File::ErrorToString(file_error);
270   base::PostTask(
271       FROM_HERE, {BrowserThread::UI},
272       base::BindOnce(&WebBundleReader::DidReconnect, this, std::move(error)));
273 }
274 
DidReconnect(base::Optional<std::string> error)275 void WebBundleReader::DidReconnect(base::Optional<std::string> error) {
276   DCHECK_EQ(state_, State::kDisconnected);
277   DCHECK(parser_);
278   auto read_tasks = std::move(pending_read_responses_);
279 
280   if (error) {
281     for (auto& pair : read_tasks) {
282       PostTask(
283           FROM_HERE,
284           base::BindOnce(std::move(pair.second), nullptr,
285                          data_decoder::mojom::BundleResponseParseError::New(
286                              data_decoder::mojom::BundleParseErrorType::
287                                  kParserInternalError,
288                              *error)));
289     }
290     return;
291   }
292 
293   state_ = State::kMetadataReady;
294   parser_->SetDisconnectCallback(base::BindOnce(
295       &WebBundleReader::OnParserDisconnected, base::Unretained(this)));
296   for (auto& pair : read_tasks)
297     ReadResponseInternal(std::move(pair.first), std::move(pair.second));
298 }
299 
ReadResponseBody(data_decoder::mojom::BundleResponsePtr response,mojo::ScopedDataPipeProducerHandle producer_handle,BodyCompletionCallback callback)300 void WebBundleReader::ReadResponseBody(
301     data_decoder::mojom::BundleResponsePtr response,
302     mojo::ScopedDataPipeProducerHandle producer_handle,
303     BodyCompletionCallback callback) {
304   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
305   DCHECK_NE(state_, State::kInitial);
306 
307   if (!blob_data_source_) {
308     DCHECK(source_->is_trusted_file() || source_->is_file());
309     auto data_producer =
310         std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle));
311     mojo::DataPipeProducer* raw_producer = data_producer.get();
312     raw_producer->Write(
313         std::make_unique<SharedFileDataSource>(file_, response->payload_offset,
314                                                response->payload_length),
315         base::BindOnce(
316             [](std::unique_ptr<mojo::DataPipeProducer> producer,
317                BodyCompletionCallback callback, MojoResult result) {
318               std::move(callback).Run(
319                   result == MOJO_RESULT_OK ? net::OK : net::ERR_UNEXPECTED);
320             },
321             std::move(data_producer), std::move(callback)));
322     return;
323   }
324 
325   DCHECK(source_->is_network());
326   blob_data_source_->ReadToDataPipe(
327       response->payload_offset, response->payload_length,
328       std::move(producer_handle), std::move(callback));
329 }
330 
HasEntry(const GURL & url) const331 bool WebBundleReader::HasEntry(const GURL& url) const {
332   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
333   DCHECK_NE(state_, State::kInitial);
334 
335   return entries_.contains(net::SimplifyUrlForRequest(url));
336 }
337 
GetPrimaryURL() const338 const GURL& WebBundleReader::GetPrimaryURL() const {
339   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
340   DCHECK_NE(state_, State::kInitial);
341 
342   return primary_url_;
343 }
344 
source() const345 const WebBundleSource& WebBundleReader::source() const {
346   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
347   return *source_;
348 }
349 
ReadMetadataInternal(MetadataCallback callback,base::File file)350 void WebBundleReader::ReadMetadataInternal(MetadataCallback callback,
351                                            base::File file) {
352   DCHECK(source_->is_trusted_file() || source_->is_file());
353   base::File::Error error = parser_->OpenFile(std::move(file));
354   if (base::File::FILE_OK != error) {
355     base::PostTask(
356         FROM_HERE, {BrowserThread::UI},
357         base::BindOnce(
358             std::move(callback),
359             data_decoder::mojom::BundleMetadataParseError::New(
360                 data_decoder::mojom::BundleParseErrorType::kParserInternalError,
361                 GURL() /* fallback_url */, base::File::ErrorToString(error))));
362   } else {
363     parser_->ParseMetadata(base::BindOnce(&WebBundleReader::OnMetadataParsed,
364                                           base::Unretained(this),
365                                           std::move(callback)));
366   }
367 }
368 
OnMetadataParsed(MetadataCallback callback,data_decoder::mojom::BundleMetadataPtr metadata,data_decoder::mojom::BundleMetadataParseErrorPtr error)369 void WebBundleReader::OnMetadataParsed(
370     MetadataCallback callback,
371     data_decoder::mojom::BundleMetadataPtr metadata,
372     data_decoder::mojom::BundleMetadataParseErrorPtr error) {
373   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
374   DCHECK_EQ(state_, State::kInitial);
375 
376   state_ = State::kMetadataReady;
377   parser_->SetDisconnectCallback(base::BindOnce(
378       &WebBundleReader::OnParserDisconnected, base::Unretained(this)));
379 
380   if (metadata) {
381     primary_url_ = metadata->primary_url;
382     entries_ = std::move(metadata->requests);
383   }
384   std::move(callback).Run(std::move(error));
385 }
386 
OnResponseParsed(ResponseCallback callback,data_decoder::mojom::BundleResponsePtr response,data_decoder::mojom::BundleResponseParseErrorPtr error)387 void WebBundleReader::OnResponseParsed(
388     ResponseCallback callback,
389     data_decoder::mojom::BundleResponsePtr response,
390     data_decoder::mojom::BundleResponseParseErrorPtr error) {
391   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
392   DCHECK_NE(state_, State::kInitial);
393 
394   std::move(callback).Run(std::move(response), std::move(error));
395 }
396 
OnParserDisconnected()397 void WebBundleReader::OnParserDisconnected() {
398   DCHECK_EQ(state_, State::kMetadataReady);
399 
400   state_ = State::kDisconnected;
401   parser_ = nullptr;
402   // Reconnection will be attempted on next ReadResponse() call.
403 }
404 
405 }  // namespace content
406