blob: 62dbebbdf1b0d59f8d25fbecb830ef6c5e060009 [file] [log] [blame]
Ken Rockot314714c2017-11-05 23:36:241// 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 "content/browser/file_url_loader_factory.h"
6
7#include <memory>
8#include <string>
9#include <utility>
10
11#include "base/bind.h"
12#include "base/files/file.h"
13#include "base/files/file_path.h"
14#include "base/files/file_util.h"
15#include "base/macros.h"
16#include "base/numerics/safe_conversions.h"
17#include "base/strings/string16.h"
18#include "base/strings/string_util.h"
19#include "base/strings/sys_string_conversions.h"
20#include "base/strings/utf_string_conversions.h"
Ken Rockot6414c4d92017-11-08 19:58:3221#include "base/task_scheduler/post_task.h"
Ken Rockot314714c2017-11-05 23:36:2422#include "base/time/time.h"
23#include "build/build_config.h"
24#include "content/public/browser/content_browser_client.h"
Ken Rockot6414c4d92017-11-08 19:58:3225#include "content/public/browser/file_url_loader.h"
Ken Rockot314714c2017-11-05 23:36:2426#include "content/public/common/url_loader.mojom.h"
27#include "mojo/public/cpp/bindings/strong_binding.h"
28#include "mojo/public/cpp/system/data_pipe.h"
29#include "mojo/public/cpp/system/file_data_pipe_producer.h"
30#include "mojo/public/cpp/system/string_data_pipe_producer.h"
31#include "net/base/directory_lister.h"
32#include "net/base/directory_listing.h"
33#include "net/base/filename_util.h"
34#include "net/base/mime_sniffer.h"
35#include "net/base/mime_util.h"
36#include "net/base/net_errors.h"
37#include "net/http/http_byte_range.h"
38#include "net/http/http_util.h"
39#include "net/url_request/redirect_info.h"
John Abd-El-Malek1df61792018-01-12 20:40:4540#include "services/network/public/cpp/resource_request.h"
Chris Mumfordb5df8ef22017-12-20 17:47:4241#include "storage/common/fileapi/file_system_util.h"
Ken Rockot314714c2017-11-05 23:36:2442#include "url/gurl.h"
43
44#if defined(OS_WIN)
45#include "base/win/shortcut.h"
46#endif
47
48namespace content {
49namespace {
50
51constexpr size_t kDefaultFileUrlPipeSize = 65536;
52
53// Because this makes things simpler.
54static_assert(kDefaultFileUrlPipeSize >= net::kMaxBytesToSniff,
55 "Default file data pipe size must be at least as large as a MIME-"
56 "type sniffing buffer.");
57
Ken Rockot6414c4d92017-11-08 19:58:3258// Policy to control how a FileURLLoader will handle directory URLs.
59enum class DirectoryLoadingPolicy {
60 // File paths which refer to directories are allowed and will load as an
61 // HTML directory listing.
62 kRespondWithListing,
63
64 // File paths which refer to directories are treated as non-existent and
65 // will result in FILE_NOT_FOUND errors.
66 kFail,
67};
68
69// Policy to control whether or not file access constraints imposed by content
70// or its embedder should be honored by a FileURLLoader.
71enum class FileAccessPolicy {
72 // Enforces file acccess policy defined by content and/or its embedder.
73 kRestricted,
74
75 // Ignores file access policy, allowing contents to be loaded from any
76 // resolvable file path given.
77 kUnrestricted,
78};
79
80// Policy to control whether or not a FileURLLoader should follow filesystem
81// links (e.g. Windows shortcuts) where applicable.
82enum class LinkFollowingPolicy {
83 kFollow,
84 kDoNotFollow,
85};
86
Ken Rockot314714c2017-11-05 23:36:2487class FileURLDirectoryLoader
88 : public mojom::URLLoader,
89 public net::DirectoryLister::DirectoryListerDelegate {
90 public:
91 static void CreateAndStart(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:4592 const network::ResourceRequest& request,
Ken Rockot314714c2017-11-05 23:36:2493 mojom::URLLoaderRequest loader,
Chris Mumfordb5df8ef22017-12-20 17:47:4294 mojom::URLLoaderClientPtrInfo client_info,
95 std::unique_ptr<FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:2496 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
97 // bindings are alive - essentially until either the client gives up or all
98 // file data has been sent to it.
99 auto* file_url_loader = new FileURLDirectoryLoader;
100 file_url_loader->Start(profile_path, request, std::move(loader),
Chris Mumfordb5df8ef22017-12-20 17:47:42101 std::move(client_info), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24102 }
103
104 // mojom::URLLoader:
105 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26106 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24107 void SetPriority(net::RequestPriority priority,
108 int32_t intra_priority_value) override {}
109 void PauseReadingBodyFromNet() override {}
110 void ResumeReadingBodyFromNet() override {}
111
112 private:
113 FileURLDirectoryLoader() : binding_(this) {}
114 ~FileURLDirectoryLoader() override = default;
115
116 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45117 const network::ResourceRequest& request,
Ken Rockot314714c2017-11-05 23:36:24118 mojom::URLLoaderRequest loader,
Chris Mumfordb5df8ef22017-12-20 17:47:42119 mojom::URLLoaderClientPtrInfo client_info,
120 std::unique_ptr<content::FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:24121 binding_.Bind(std::move(loader));
122 binding_.set_connection_error_handler(base::BindOnce(
123 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
124
125 mojom::URLLoaderClientPtr client;
126 client.Bind(std::move(client_info));
127
128 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26129 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24130 return;
131 }
132
133 base::File::Info info;
134 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26135 client->OnComplete(
136 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24137 return;
138 }
139
140 if (!GetContentClient()->browser()->IsFileAccessAllowed(
141 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26142 client->OnComplete(
143 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24144 return;
145 }
146
147 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
148 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26149 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24150 return;
151 }
152
153 ResourceResponseHead head;
154 head.mime_type = "text/html";
155 head.charset = "utf-8";
156 client->OnReceiveResponse(head, base::nullopt, nullptr);
157 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
158 client_ = std::move(client);
159
160 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
161 lister_->Start();
162
163 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
164 std::move(pipe.producer_handle));
165 }
166
167 void OnConnectionError() {
168 binding_.Close();
169 MaybeDeleteSelf();
170 }
171
172 void MaybeDeleteSelf() {
173 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
174 delete this;
175 }
176
177 // net::DirectoryLister::DirectoryListerDelegate:
178 void OnListFile(
179 const net::DirectoryLister::DirectoryListerData& data) override {
180 if (!wrote_header_) {
181 wrote_header_ = true;
182
183#if defined(OS_WIN)
184 const base::string16& title = path_.value();
185#elif defined(OS_POSIX)
186 const base::string16& title =
187 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
188#endif
189 pending_data_.append(net::GetDirectoryListingHeader(title));
190
191 // If not a top-level directory, add a link to the parent directory. To
192 // figure this out, first normalize |path_| by stripping trailing
193 // separators. Then compare the result to its DirName(). For the top-level
194 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
195 // DirName().
196 base::FilePath stripped_path = path_.StripTrailingSeparators();
197 if (stripped_path != stripped_path.DirName())
198 pending_data_.append(net::GetParentDirectoryLink());
199 }
200
201 // Skip current / parent links from the listing.
202 base::FilePath filename = data.info.GetName();
203 if (filename.value() != base::FilePath::kCurrentDirectory &&
204 filename.value() != base::FilePath::kParentDirectory) {
205#if defined(OS_WIN)
206 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
207#elif defined(OS_POSIX)
208 const std::string& raw_bytes = filename.value();
209#endif
210 pending_data_.append(net::GetDirectoryListingEntry(
211 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
212 data.info.GetSize(), data.info.GetLastModifiedTime()));
213 }
214
215 MaybeTransferPendingData();
216 }
217
218 void OnListDone(int error) override {
219 listing_result_ = error;
220 lister_.reset();
221 MaybeDeleteSelf();
222 }
223
224 void MaybeTransferPendingData() {
225 if (transfer_in_progress_)
226 return;
227
228 transfer_in_progress_ = true;
229 data_producer_->Write(pending_data_,
230 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
231 base::Unretained(this)));
232 // The producer above will have already copied any parts of |pending_data_|
233 // that couldn't be written immediately, so we can wipe it out here to begin
234 // accumulating more data.
235 pending_data_.clear();
236 }
237
238 void OnDataWritten(MojoResult result) {
239 transfer_in_progress_ = false;
240
241 int completion_status;
242 if (result == MOJO_RESULT_OK) {
243 if (!pending_data_.empty()) {
244 // Keep flushing the data buffer as long as it's non-empty and pipe
245 // writes are succeeding.
246 MaybeTransferPendingData();
247 return;
248 }
249
250 // If there's no pending data but the lister is still active, we simply
251 // wait for more listing results.
252 if (lister_)
253 return;
254
255 // At this point we know the listing is complete and all available data
256 // has been transferred. We inherit the status of the listing operation.
257 completion_status = listing_result_;
258 } else {
259 completion_status = net::ERR_FAILED;
260 }
261
arthursonzogni3a4ca9f2017-12-07 17:58:34262 // All the data has been written now. Close the data pipe. The consumer will
263 // be notified that there will be no more data to read from now.
264 data_producer_.reset();
265
Takashi Toyoshimaaa278662017-11-20 11:11:26266 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24267 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34268
Ken Rockot314714c2017-11-05 23:36:24269 MaybeDeleteSelf();
270 }
271
272 base::FilePath path_;
273 std::unique_ptr<net::DirectoryLister> lister_;
274 bool wrote_header_ = false;
275 int listing_result_;
276
277 mojo::Binding<mojom::URLLoader> binding_;
278 mojom::URLLoaderClientPtr client_;
279
280 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
281 std::string pending_data_;
282 bool transfer_in_progress_ = false;
283
284 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
285};
286
287class FileURLLoader : public mojom::URLLoader {
288 public:
289 static void CreateAndStart(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45290 const network::ResourceRequest& request,
Ken Rockot314714c2017-11-05 23:36:24291 mojom::URLLoaderRequest loader,
Ken Rockot6414c4d92017-11-08 19:58:32292 mojom::URLLoaderClientPtrInfo client_info,
293 DirectoryLoadingPolicy directory_loading_policy,
294 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42295 LinkFollowingPolicy link_following_policy,
296 std::unique_ptr<FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:24297 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
298 // bindings are alive - essentially until either the client gives up or all
299 // file data has been sent to it.
300 auto* file_url_loader = new FileURLLoader;
301 file_url_loader->Start(profile_path, request, std::move(loader),
Ken Rockot6414c4d92017-11-08 19:58:32302 std::move(client_info), directory_loading_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42303 file_access_policy, link_following_policy,
304 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24305 }
306
307 // mojom::URLLoader:
308 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26309 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24310 void SetPriority(net::RequestPriority priority,
311 int32_t intra_priority_value) override {}
312 void PauseReadingBodyFromNet() override {}
313 void ResumeReadingBodyFromNet() override {}
314
315 private:
316 FileURLLoader() : binding_(this) {}
317 ~FileURLLoader() override = default;
318
319 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45320 const network::ResourceRequest& request,
Ken Rockot314714c2017-11-05 23:36:24321 mojom::URLLoaderRequest loader,
Ken Rockot6414c4d92017-11-08 19:58:32322 mojom::URLLoaderClientPtrInfo client_info,
323 DirectoryLoadingPolicy directory_loading_policy,
324 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42325 LinkFollowingPolicy link_following_policy,
326 std::unique_ptr<FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:24327 ResourceResponseHead head;
328 head.request_start = base::TimeTicks::Now();
329 head.response_start = base::TimeTicks::Now();
330 binding_.Bind(std::move(loader));
331 binding_.set_connection_error_handler(base::BindOnce(
332 &FileURLLoader::OnConnectionError, base::Unretained(this)));
333
334 mojom::URLLoaderClientPtr client;
335 client.Bind(std::move(client_info));
336
337 base::FilePath path;
338 if (!net::FileURLToFilePath(request.url, &path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26339 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42340 if (observer)
341 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24342 return;
343 }
344
345 base::File::Info info;
346 if (!base::GetFileInfo(path, &info)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26347 client->OnComplete(
348 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42349 if (observer)
350 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24351 return;
352 }
353
Ken Rockot6414c4d92017-11-08 19:58:32354 if (info.is_directory) {
355 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26356 client->OnComplete(
357 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42358 if (observer)
359 observer->OnDoneReading();
Ken Rockot6414c4d92017-11-08 19:58:32360 return;
361 }
Ken Rockot314714c2017-11-05 23:36:24362
Ken Rockot6414c4d92017-11-08 19:58:32363 DCHECK_EQ(directory_loading_policy,
364 DirectoryLoadingPolicy::kRespondWithListing);
365
366 GURL directory_url = request.url;
367 if (!path.EndsWithSeparator()) {
368 // If the named path is a directory with no trailing slash, redirect to
369 // the same path, but with a trailing slash.
370 std::string new_path = directory_url.path() + '/';
371 GURL::Replacements replacements;
372 replacements.SetPathStr(new_path);
373 directory_url = directory_url.ReplaceComponents(replacements);
374
375 net::RedirectInfo redirect_info;
376 redirect_info.new_method = "GET";
377 redirect_info.status_code = 301;
378 redirect_info.new_url = directory_url;
379 head.encoded_data_length = 0;
380 client->OnReceiveRedirect(redirect_info, head);
381 }
Ken Rockot314714c2017-11-05 23:36:24382
383 // Restart the request with a directory loader.
John Abd-El-Malek1df61792018-01-12 20:40:45384 network::ResourceRequest new_request = request;
Ken Rockot6414c4d92017-11-08 19:58:32385 new_request.url = directory_url;
Ken Rockot314714c2017-11-05 23:36:24386 FileURLDirectoryLoader::CreateAndStart(
Chris Mumfordb5df8ef22017-12-20 17:47:42387 profile_path, new_request, binding_.Unbind(), client.PassInterface(),
388 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24389 MaybeDeleteSelf();
390 return;
391 }
392
393#if defined(OS_WIN)
394 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32395 if (link_following_policy == LinkFollowingPolicy::kFollow &&
396 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24397 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
398 // Follow Windows shortcuts
399 GURL new_url = net::FilePathToFileURL(shortcut_target);
400
401 net::RedirectInfo redirect_info;
402 redirect_info.new_method = "GET";
403 redirect_info.status_code = 301;
404 redirect_info.new_url = new_url;
405 head.encoded_data_length = 0;
406 client->OnReceiveRedirect(redirect_info, head);
407
408 // Restart the request with the new URL.
John Abd-El-Malek1df61792018-01-12 20:40:45409 network::ResourceRequest new_request = request;
Ken Rockot314714c2017-11-05 23:36:24410 new_request.url = redirect_info.new_url;
411 return Start(profile_path, request, binding_.Unbind(),
Ken Rockot6414c4d92017-11-08 19:58:32412 client.PassInterface(), directory_loading_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42413 file_access_policy, link_following_policy,
414 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24415 }
416#endif // defined(OS_WIN)
417
Ken Rockot6414c4d92017-11-08 19:58:32418 if (file_access_policy == FileAccessPolicy::kRestricted &&
419 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24420 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26421 client->OnComplete(
422 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Chris Mumfordb5df8ef22017-12-20 17:47:42423 if (observer)
424 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24425 return;
426 }
427
428 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
429 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26430 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42431 if (observer)
432 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24433 return;
434 }
435
436 // Should never be possible for this to be a directory. If FileURLLoader
437 // is used to respond to a directory request, it must be because the URL
438 // path didn't have a trailing path separator. In that case we finish with
439 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
440 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42441 if (observer)
442 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24443
444 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumfordb5df8ef22017-12-20 17:47:42445 if (observer)
446 observer->OnOpenComplete(net::FileErrorToNetError(file.error_details()));
Ken Rockot314714c2017-11-05 23:36:24447 char initial_read_buffer[net::kMaxBytesToSniff];
448 int initial_read_result =
449 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
450 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42451 base::File::Error read_error = base::File::GetLastFileError();
452 DCHECK_NE(base::File::FILE_OK, read_error);
453 if (observer) {
454 observer->OnBytesRead(nullptr, 0u, read_error);
455 observer->OnDoneReading();
456 }
457 net::Error net_error = net::FileErrorToNetError(read_error);
458 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
Ken Rockot314714c2017-11-05 23:36:24459 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42460 } else if (observer) {
461 observer->OnBytesRead(initial_read_buffer, initial_read_result,
462 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24463 }
464 size_t initial_read_size = static_cast<size_t>(initial_read_result);
465
466 std::string range_header;
467 net::HttpByteRange byte_range;
468 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
469 &range_header)) {
470 // Handle a simple Range header for a single range.
471 std::vector<net::HttpByteRange> ranges;
472 bool fail = false;
473 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
474 ranges.size() == 1) {
475 byte_range = ranges[0];
476 if (!byte_range.ComputeBounds(info.size))
477 fail = true;
478 } else {
479 fail = true;
480 }
481
482 if (fail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26483 client->OnComplete(network::URLLoaderCompletionStatus(
484 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
Chris Mumfordb5df8ef22017-12-20 17:47:42485 if (observer)
486 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24487 return;
488 }
489 }
490
491 size_t first_byte_to_send = 0;
492 size_t total_bytes_to_send = static_cast<size_t>(info.size);
493 if (byte_range.IsValid()) {
494 first_byte_to_send =
495 static_cast<size_t>(byte_range.first_byte_position());
496 total_bytes_to_send =
497 static_cast<size_t>(byte_range.last_byte_position()) -
498 first_byte_to_send + 1;
499 }
500
501 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
502
503 if (first_byte_to_send < initial_read_size) {
504 // Write any data we read for MIME sniffing, constraining by range where
505 // applicable. This will always fit in the pipe (see assertion near
506 // |kDefaultFileUrlPipeSize| definition).
507 uint32_t write_size = std::min(
508 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
509 static_cast<uint32_t>(total_bytes_to_send));
510 const uint32_t expected_write_size = write_size;
511 MojoResult result = pipe.producer_handle->WriteData(
512 &initial_read_buffer[first_byte_to_send], &write_size,
513 MOJO_WRITE_DATA_FLAG_NONE);
514 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42515 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24516 return;
517 }
518
519 // Discount the bytes we just sent from the total range.
520 first_byte_to_send = initial_read_size;
521 total_bytes_to_send -= write_size;
522 }
523
524 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
525 net::SniffMimeType(initial_read_buffer, initial_read_result, request.url,
526 head.mime_type, &head.mime_type);
527 }
528 client->OnReceiveResponse(head, base::nullopt, nullptr);
529 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
530 client_ = std::move(client);
531
532 if (total_bytes_to_send == 0) {
533 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42534 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24535 return;
536 }
537
538 // In case of a range request, seek to the appropriate position before
539 // sending the remaining bytes asynchronously. Under normal conditions
540 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42541 int new_position = file.Seek(base::File::FROM_BEGIN,
542 static_cast<int64_t>(first_byte_to_send));
543 if (observer)
544 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24545
546 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42547 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24548 data_producer_->WriteFromFile(
549 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42550 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
551 nullptr));
Ken Rockot314714c2017-11-05 23:36:24552 }
553
554 void OnConnectionError() {
555 binding_.Close();
556 MaybeDeleteSelf();
557 }
558
559 void MaybeDeleteSelf() {
560 if (!binding_.is_bound() && !client_.is_bound())
561 delete this;
562 }
563
Chris Mumfordb5df8ef22017-12-20 17:47:42564 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
565 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34566 // All the data has been written now. Close the data pipe. The consumer will
567 // be notified that there will be no more data to read from now.
568 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42569 if (observer)
570 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34571
Ken Rockot314714c2017-11-05 23:36:24572 if (result == MOJO_RESULT_OK)
Takashi Toyoshimaaa278662017-11-20 11:11:26573 client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
Ken Rockot314714c2017-11-05 23:36:24574 else
Takashi Toyoshimaaa278662017-11-20 11:11:26575 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24576 client_.reset();
577 MaybeDeleteSelf();
578 }
579
580 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
581 mojo::Binding<mojom::URLLoader> binding_;
582 mojom::URLLoaderClientPtr client_;
583
584 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
585};
586
587} // namespace
588
589FileURLLoaderFactory::FileURLLoaderFactory(
590 const base::FilePath& profile_path,
591 scoped_refptr<base::SequencedTaskRunner> task_runner)
592 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
593
594FileURLLoaderFactory::~FileURLLoaderFactory() = default;
595
Ken Rockot314714c2017-11-05 23:36:24596void FileURLLoaderFactory::CreateLoaderAndStart(
597 mojom::URLLoaderRequest loader,
598 int32_t routing_id,
599 int32_t request_id,
600 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45601 const network::ResourceRequest& request,
Ken Rockot314714c2017-11-05 23:36:24602 mojom::URLLoaderClientPtr client,
603 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
604 base::FilePath file_path;
605 const bool is_file = net::FileURLToFilePath(request.url, &file_path);
606 if (is_file && file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
607 task_runner_->PostTask(
608 FROM_HERE,
609 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42610 request, std::move(loader), client.PassInterface(),
611 std::unique_ptr<FileURLLoaderObserver>()));
Ken Rockot314714c2017-11-05 23:36:24612 } else {
613 task_runner_->PostTask(
614 FROM_HERE,
615 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32616 std::move(loader), client.PassInterface(),
617 DirectoryLoadingPolicy::kRespondWithListing,
618 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42619 LinkFollowingPolicy::kFollow,
620 std::unique_ptr<FileURLLoaderObserver>()));
Ken Rockot314714c2017-11-05 23:36:24621 }
622}
623
624void FileURLLoaderFactory::Clone(mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32625 bindings_.AddBinding(this, std::move(loader));
626}
627
John Abd-El-Malek1df61792018-01-12 20:40:45628void CreateFileURLLoader(const network::ResourceRequest& request,
Ken Rockot6414c4d92017-11-08 19:58:32629 mojom::URLLoaderRequest loader,
Chris Mumfordb5df8ef22017-12-20 17:47:42630 mojom::URLLoaderClientPtr client,
631 std::unique_ptr<FileURLLoaderObserver> observer) {
Ken Rockot6414c4d92017-11-08 19:58:32632 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
633 {base::MayBlock(), base::TaskPriority::BACKGROUND,
634 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
635 task_runner->PostTask(
636 FROM_HERE,
637 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
638 std::move(loader), client.PassInterface(),
639 DirectoryLoadingPolicy::kFail,
640 FileAccessPolicy::kUnrestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42641 LinkFollowingPolicy::kDoNotFollow, std::move(observer)));
Ken Rockot314714c2017-11-05 23:36:24642}
643
644} // namespace content