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