blob: 25c2df3f539b8c8842bd662f516cffd04fd4b8fe [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>
Chris Mumford8f812662018-02-22 00:27:5710#include <vector>
Ken Rockot314714c2017-11-05 23:36:2411
12#include "base/bind.h"
13#include "base/files/file.h"
14#include "base/files/file_path.h"
15#include "base/files/file_util.h"
16#include "base/macros.h"
17#include "base/numerics/safe_conversions.h"
18#include "base/strings/string16.h"
19#include "base/strings/string_util.h"
20#include "base/strings/sys_string_conversions.h"
21#include "base/strings/utf_string_conversions.h"
Ken Rockot6414c4d92017-11-08 19:58:3222#include "base/task_scheduler/post_task.h"
Ken Rockot314714c2017-11-05 23:36:2423#include "base/time/time.h"
24#include "build/build_config.h"
25#include "content/public/browser/content_browser_client.h"
Ken Rockot6414c4d92017-11-08 19:58:3226#include "content/public/browser/file_url_loader.h"
Matt Menkefcbb1bd72018-01-31 21:53:1227#include "content/public/common/content_client.h"
Ken Rockot314714c2017-11-05 23:36:2428#include "mojo/public/cpp/bindings/strong_binding.h"
29#include "mojo/public/cpp/system/data_pipe.h"
30#include "mojo/public/cpp/system/file_data_pipe_producer.h"
31#include "mojo/public/cpp/system/string_data_pipe_producer.h"
32#include "net/base/directory_lister.h"
33#include "net/base/directory_listing.h"
34#include "net/base/filename_util.h"
35#include "net/base/mime_sniffer.h"
36#include "net/base/mime_util.h"
37#include "net/base/net_errors.h"
38#include "net/http/http_byte_range.h"
39#include "net/http/http_util.h"
40#include "net/url_request/redirect_info.h"
John Abd-El-Malek1df61792018-01-12 20:40:4541#include "services/network/public/cpp/resource_request.h"
Ken Rockot54311e62018-02-10 19:01:5242#include "services/network/public/mojom/url_loader.mojom.h"
Chris Mumfordb5df8ef22017-12-20 17:47:4243#include "storage/common/fileapi/file_system_util.h"
Ken Rockot314714c2017-11-05 23:36:2444#include "url/gurl.h"
45
46#if defined(OS_WIN)
47#include "base/win/shortcut.h"
48#endif
49
50namespace content {
51namespace {
52
53constexpr size_t kDefaultFileUrlPipeSize = 65536;
54
55// Because this makes things simpler.
56static_assert(kDefaultFileUrlPipeSize >= net::kMaxBytesToSniff,
57 "Default file data pipe size must be at least as large as a MIME-"
58 "type sniffing buffer.");
59
Ken Rockot6414c4d92017-11-08 19:58:3260// Policy to control how a FileURLLoader will handle directory URLs.
61enum class DirectoryLoadingPolicy {
62 // File paths which refer to directories are allowed and will load as an
63 // HTML directory listing.
64 kRespondWithListing,
65
66 // File paths which refer to directories are treated as non-existent and
67 // will result in FILE_NOT_FOUND errors.
68 kFail,
69};
70
71// Policy to control whether or not file access constraints imposed by content
72// or its embedder should be honored by a FileURLLoader.
73enum class FileAccessPolicy {
74 // Enforces file acccess policy defined by content and/or its embedder.
75 kRestricted,
76
77 // Ignores file access policy, allowing contents to be loaded from any
78 // resolvable file path given.
79 kUnrestricted,
80};
81
82// Policy to control whether or not a FileURLLoader should follow filesystem
83// links (e.g. Windows shortcuts) where applicable.
84enum class LinkFollowingPolicy {
85 kFollow,
86 kDoNotFollow,
87};
88
Ken Rockot314714c2017-11-05 23:36:2489class FileURLDirectoryLoader
John Abd-El-Malekb165dc52018-01-18 17:12:1890 : public network::mojom::URLLoader,
Ken Rockot314714c2017-11-05 23:36:2491 public net::DirectoryLister::DirectoryListerDelegate {
92 public:
Chris Mumford8f812662018-02-22 00:27:5793 static void CreateAndStart(
94 const base::FilePath& profile_path,
95 const network::ResourceRequest& request,
96 network::mojom::URLLoaderRequest loader,
97 network::mojom::URLLoaderClientPtrInfo client_info,
98 std::unique_ptr<FileURLLoaderObserver> observer,
99 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24100 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
101 // bindings are alive - essentially until either the client gives up or all
102 // file data has been sent to it.
103 auto* file_url_loader = new FileURLDirectoryLoader;
104 file_url_loader->Start(profile_path, request, std::move(loader),
Chris Mumford8f812662018-02-22 00:27:57105 std::move(client_info), std::move(observer),
106 std::move(response_headers));
Ken Rockot314714c2017-11-05 23:36:24107 }
108
John Abd-El-Malekb165dc52018-01-18 17:12:18109 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20110 void FollowRedirect(const base::Optional<std::vector<std::string>>&
111 to_be_removed_request_headers,
112 const base::Optional<net::HttpRequestHeaders>&
Chong Zhangf19dde92018-05-23 04:33:59113 modified_request_headers) override {}
arthursonzogni2e1524a72017-12-18 16:53:26114 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24115 void SetPriority(net::RequestPriority priority,
116 int32_t intra_priority_value) override {}
117 void PauseReadingBodyFromNet() override {}
118 void ResumeReadingBodyFromNet() override {}
119
120 private:
121 FileURLDirectoryLoader() : binding_(this) {}
122 ~FileURLDirectoryLoader() override = default;
123
124 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45125 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18126 network::mojom::URLLoaderRequest loader,
127 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumford8f812662018-02-22 00:27:57128 std::unique_ptr<content::FileURLLoaderObserver> observer,
129 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24130 binding_.Bind(std::move(loader));
131 binding_.set_connection_error_handler(base::BindOnce(
132 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
133
John Abd-El-Malekb165dc52018-01-18 17:12:18134 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24135 client.Bind(std::move(client_info));
136
137 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26138 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24139 return;
140 }
141
142 base::File::Info info;
143 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26144 client->OnComplete(
145 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24146 return;
147 }
148
149 if (!GetContentClient()->browser()->IsFileAccessAllowed(
150 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26151 client->OnComplete(
152 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24153 return;
154 }
155
156 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
157 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26158 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24159 return;
160 }
161
John Abd-El-Malek46248032018-01-17 19:11:23162 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24163 head.mime_type = "text/html";
164 head.charset = "utf-8";
Marijn Kruisselbrink9ebd7ba2018-06-11 23:18:04165 client->OnReceiveResponse(head);
Ken Rockot314714c2017-11-05 23:36:24166 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
167 client_ = std::move(client);
168
169 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
170 lister_->Start();
171
172 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
173 std::move(pipe.producer_handle));
174 }
175
176 void OnConnectionError() {
177 binding_.Close();
178 MaybeDeleteSelf();
179 }
180
181 void MaybeDeleteSelf() {
182 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
183 delete this;
184 }
185
186 // net::DirectoryLister::DirectoryListerDelegate:
187 void OnListFile(
188 const net::DirectoryLister::DirectoryListerData& data) override {
189 if (!wrote_header_) {
190 wrote_header_ = true;
191
192#if defined(OS_WIN)
193 const base::string16& title = path_.value();
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48194#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24195 const base::string16& title =
196 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
197#endif
198 pending_data_.append(net::GetDirectoryListingHeader(title));
199
200 // If not a top-level directory, add a link to the parent directory. To
201 // figure this out, first normalize |path_| by stripping trailing
202 // separators. Then compare the result to its DirName(). For the top-level
203 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
204 // DirName().
205 base::FilePath stripped_path = path_.StripTrailingSeparators();
206 if (stripped_path != stripped_path.DirName())
207 pending_data_.append(net::GetParentDirectoryLink());
208 }
209
210 // Skip current / parent links from the listing.
211 base::FilePath filename = data.info.GetName();
212 if (filename.value() != base::FilePath::kCurrentDirectory &&
213 filename.value() != base::FilePath::kParentDirectory) {
214#if defined(OS_WIN)
215 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48216#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24217 const std::string& raw_bytes = filename.value();
218#endif
219 pending_data_.append(net::GetDirectoryListingEntry(
220 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
221 data.info.GetSize(), data.info.GetLastModifiedTime()));
222 }
223
224 MaybeTransferPendingData();
225 }
226
227 void OnListDone(int error) override {
228 listing_result_ = error;
229 lister_.reset();
230 MaybeDeleteSelf();
231 }
232
233 void MaybeTransferPendingData() {
234 if (transfer_in_progress_)
235 return;
236
237 transfer_in_progress_ = true;
238 data_producer_->Write(pending_data_,
Daniel Murphy0ecba612018-02-07 20:56:18239 mojo::StringDataPipeProducer::AsyncWritingMode::
240 STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
Ken Rockot314714c2017-11-05 23:36:24241 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
242 base::Unretained(this)));
243 // The producer above will have already copied any parts of |pending_data_|
244 // that couldn't be written immediately, so we can wipe it out here to begin
245 // accumulating more data.
246 pending_data_.clear();
247 }
248
249 void OnDataWritten(MojoResult result) {
250 transfer_in_progress_ = false;
251
252 int completion_status;
253 if (result == MOJO_RESULT_OK) {
254 if (!pending_data_.empty()) {
255 // Keep flushing the data buffer as long as it's non-empty and pipe
256 // writes are succeeding.
257 MaybeTransferPendingData();
258 return;
259 }
260
261 // If there's no pending data but the lister is still active, we simply
262 // wait for more listing results.
263 if (lister_)
264 return;
265
266 // At this point we know the listing is complete and all available data
267 // has been transferred. We inherit the status of the listing operation.
268 completion_status = listing_result_;
269 } else {
270 completion_status = net::ERR_FAILED;
271 }
272
arthursonzogni3a4ca9f2017-12-07 17:58:34273 // All the data has been written now. Close the data pipe. The consumer will
274 // be notified that there will be no more data to read from now.
275 data_producer_.reset();
276
Takashi Toyoshimaaa278662017-11-20 11:11:26277 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24278 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34279
Ken Rockot314714c2017-11-05 23:36:24280 MaybeDeleteSelf();
281 }
282
283 base::FilePath path_;
284 std::unique_ptr<net::DirectoryLister> lister_;
285 bool wrote_header_ = false;
286 int listing_result_;
287
John Abd-El-Malekb165dc52018-01-18 17:12:18288 mojo::Binding<network::mojom::URLLoader> binding_;
289 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24290
291 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
292 std::string pending_data_;
293 bool transfer_in_progress_ = false;
294
295 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
296};
297
John Abd-El-Malekb165dc52018-01-18 17:12:18298class FileURLLoader : public network::mojom::URLLoader {
Ken Rockot314714c2017-11-05 23:36:24299 public:
Ken Rockota0dfaca12018-02-15 07:26:25300 static void CreateAndStart(
301 const base::FilePath& profile_path,
302 const network::ResourceRequest& request,
303 network::mojom::URLLoaderRequest loader,
304 network::mojom::URLLoaderClientPtrInfo client_info,
305 DirectoryLoadingPolicy directory_loading_policy,
306 FileAccessPolicy file_access_policy,
307 LinkFollowingPolicy link_following_policy,
308 std::unique_ptr<FileURLLoaderObserver> observer,
309 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot314714c2017-11-05 23:36:24310 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
311 // bindings are alive - essentially until either the client gives up or all
312 // file data has been sent to it.
313 auto* file_url_loader = new FileURLLoader;
Ken Rockota0dfaca12018-02-15 07:26:25314 file_url_loader->Start(
315 profile_path, request, std::move(loader), std::move(client_info),
316 directory_loading_policy, file_access_policy, link_following_policy,
317 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24318 }
319
John Abd-El-Malekb165dc52018-01-18 17:12:18320 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20321 void FollowRedirect(const base::Optional<std::vector<std::string>>&
322 to_be_removed_request_headers,
323 const base::Optional<net::HttpRequestHeaders>&
Chong Zhangf19dde92018-05-23 04:33:59324 modified_request_headers) override {}
arthursonzogni2e1524a72017-12-18 16:53:26325 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24326 void SetPriority(net::RequestPriority priority,
327 int32_t intra_priority_value) override {}
328 void PauseReadingBodyFromNet() override {}
329 void ResumeReadingBodyFromNet() override {}
330
331 private:
332 FileURLLoader() : binding_(this) {}
333 ~FileURLLoader() override = default;
334
335 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45336 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18337 network::mojom::URLLoaderRequest loader,
338 network::mojom::URLLoaderClientPtrInfo client_info,
Ken Rockot6414c4d92017-11-08 19:58:32339 DirectoryLoadingPolicy directory_loading_policy,
340 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42341 LinkFollowingPolicy link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25342 std::unique_ptr<FileURLLoaderObserver> observer,
343 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
John Abd-El-Malek46248032018-01-17 19:11:23344 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24345 head.request_start = base::TimeTicks::Now();
346 head.response_start = base::TimeTicks::Now();
Ken Rockota0dfaca12018-02-15 07:26:25347 head.headers = extra_response_headers;
Ken Rockot314714c2017-11-05 23:36:24348 binding_.Bind(std::move(loader));
349 binding_.set_connection_error_handler(base::BindOnce(
350 &FileURLLoader::OnConnectionError, base::Unretained(this)));
351
John Abd-El-Malekb165dc52018-01-18 17:12:18352 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24353 client.Bind(std::move(client_info));
354
355 base::FilePath path;
356 if (!net::FileURLToFilePath(request.url, &path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26357 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42358 if (observer)
359 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24360 return;
361 }
362
363 base::File::Info info;
364 if (!base::GetFileInfo(path, &info)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26365 client->OnComplete(
366 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42367 if (observer)
368 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24369 return;
370 }
371
Ken Rockot6414c4d92017-11-08 19:58:32372 if (info.is_directory) {
373 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26374 client->OnComplete(
375 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42376 if (observer)
377 observer->OnDoneReading();
Ken Rockot6414c4d92017-11-08 19:58:32378 return;
379 }
Ken Rockot314714c2017-11-05 23:36:24380
Ken Rockot6414c4d92017-11-08 19:58:32381 DCHECK_EQ(directory_loading_policy,
382 DirectoryLoadingPolicy::kRespondWithListing);
383
384 GURL directory_url = request.url;
385 if (!path.EndsWithSeparator()) {
386 // If the named path is a directory with no trailing slash, redirect to
387 // the same path, but with a trailing slash.
388 std::string new_path = directory_url.path() + '/';
389 GURL::Replacements replacements;
390 replacements.SetPathStr(new_path);
391 directory_url = directory_url.ReplaceComponents(replacements);
392
393 net::RedirectInfo redirect_info;
394 redirect_info.new_method = "GET";
395 redirect_info.status_code = 301;
396 redirect_info.new_url = directory_url;
397 head.encoded_data_length = 0;
398 client->OnReceiveRedirect(redirect_info, head);
399 }
Ken Rockot314714c2017-11-05 23:36:24400
401 // Restart the request with a directory loader.
John Abd-El-Malek1df61792018-01-12 20:40:45402 network::ResourceRequest new_request = request;
Ken Rockot6414c4d92017-11-08 19:58:32403 new_request.url = directory_url;
Ken Rockot314714c2017-11-05 23:36:24404 FileURLDirectoryLoader::CreateAndStart(
Chris Mumfordb5df8ef22017-12-20 17:47:42405 profile_path, new_request, binding_.Unbind(), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57406 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24407 MaybeDeleteSelf();
408 return;
409 }
410
411#if defined(OS_WIN)
412 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32413 if (link_following_policy == LinkFollowingPolicy::kFollow &&
414 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24415 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
416 // Follow Windows shortcuts
417 GURL new_url = net::FilePathToFileURL(shortcut_target);
418
419 net::RedirectInfo redirect_info;
420 redirect_info.new_method = "GET";
421 redirect_info.status_code = 301;
422 redirect_info.new_url = new_url;
423 head.encoded_data_length = 0;
424 client->OnReceiveRedirect(redirect_info, head);
425
426 // Restart the request with the new URL.
John Abd-El-Malek1df61792018-01-12 20:40:45427 network::ResourceRequest new_request = request;
Ken Rockot314714c2017-11-05 23:36:24428 new_request.url = redirect_info.new_url;
429 return Start(profile_path, request, binding_.Unbind(),
Ken Rockot6414c4d92017-11-08 19:58:32430 client.PassInterface(), directory_loading_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42431 file_access_policy, link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25432 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24433 }
434#endif // defined(OS_WIN)
435
Ken Rockot6414c4d92017-11-08 19:58:32436 if (file_access_policy == FileAccessPolicy::kRestricted &&
437 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24438 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26439 client->OnComplete(
440 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Chris Mumfordb5df8ef22017-12-20 17:47:42441 if (observer)
442 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24443 return;
444 }
445
446 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
447 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26448 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42449 if (observer)
450 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24451 return;
452 }
453
454 // Should never be possible for this to be a directory. If FileURLLoader
455 // is used to respond to a directory request, it must be because the URL
456 // path didn't have a trailing path separator. In that case we finish with
457 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
458 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42459 if (observer)
460 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24461
462 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumford8f812662018-02-22 00:27:57463 if (!file.IsValid()) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10464 if (observer) {
465 observer->OnBytesRead(nullptr, 0u, file.error_details());
Chris Mumford8f812662018-02-22 00:27:57466 observer->OnDoneReading();
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10467 }
468 net::Error net_error = net::FileErrorToNetError(file.error_details());
Chris Mumford8f812662018-02-22 00:27:57469 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
470 MaybeDeleteSelf();
471 return;
472 }
Ken Rockot314714c2017-11-05 23:36:24473 char initial_read_buffer[net::kMaxBytesToSniff];
474 int initial_read_result =
475 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
476 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42477 base::File::Error read_error = base::File::GetLastFileError();
478 DCHECK_NE(base::File::FILE_OK, read_error);
479 if (observer) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10480 // This can happen when the file is unreadable (which can happen during
481 // corruption). We need to be sure to inform
482 // the observer that we've finished reading so that it can proceed.
Chris Mumfordb5df8ef22017-12-20 17:47:42483 observer->OnBytesRead(nullptr, 0u, read_error);
484 observer->OnDoneReading();
485 }
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10486 net::Error net_error = net::FileErrorToNetError(read_error);
Chris Mumfordb5df8ef22017-12-20 17:47:42487 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
Ken Rockot314714c2017-11-05 23:36:24488 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42489 } else if (observer) {
490 observer->OnBytesRead(initial_read_buffer, initial_read_result,
491 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24492 }
493 size_t initial_read_size = static_cast<size_t>(initial_read_result);
494
495 std::string range_header;
496 net::HttpByteRange byte_range;
497 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
498 &range_header)) {
499 // Handle a simple Range header for a single range.
500 std::vector<net::HttpByteRange> ranges;
501 bool fail = false;
502 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
503 ranges.size() == 1) {
504 byte_range = ranges[0];
505 if (!byte_range.ComputeBounds(info.size))
506 fail = true;
507 } else {
508 fail = true;
509 }
510
511 if (fail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26512 client->OnComplete(network::URLLoaderCompletionStatus(
513 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
Chris Mumfordb5df8ef22017-12-20 17:47:42514 if (observer)
515 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24516 return;
517 }
518 }
519
520 size_t first_byte_to_send = 0;
521 size_t total_bytes_to_send = static_cast<size_t>(info.size);
522 if (byte_range.IsValid()) {
523 first_byte_to_send =
524 static_cast<size_t>(byte_range.first_byte_position());
525 total_bytes_to_send =
526 static_cast<size_t>(byte_range.last_byte_position()) -
527 first_byte_to_send + 1;
528 }
529
530 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
531
532 if (first_byte_to_send < initial_read_size) {
533 // Write any data we read for MIME sniffing, constraining by range where
534 // applicable. This will always fit in the pipe (see assertion near
535 // |kDefaultFileUrlPipeSize| definition).
536 uint32_t write_size = std::min(
537 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
538 static_cast<uint32_t>(total_bytes_to_send));
539 const uint32_t expected_write_size = write_size;
540 MojoResult result = pipe.producer_handle->WriteData(
541 &initial_read_buffer[first_byte_to_send], &write_size,
542 MOJO_WRITE_DATA_FLAG_NONE);
543 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42544 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24545 return;
546 }
547
548 // Discount the bytes we just sent from the total range.
549 first_byte_to_send = initial_read_size;
550 total_bytes_to_send -= write_size;
551 }
552
553 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
Matt Menkefcbb1bd72018-01-31 21:53:12554 net::SniffMimeType(
555 initial_read_buffer, initial_read_result, request.url, head.mime_type,
556 GetContentClient()->browser()->ForceSniffingFileUrlsForHtml()
557 ? net::ForceSniffFileUrlsForHtml::kEnabled
558 : net::ForceSniffFileUrlsForHtml::kDisabled,
559 &head.mime_type);
Ken Rockot314714c2017-11-05 23:36:24560 }
Chris Mumford8f812662018-02-22 00:27:57561 if (head.headers) {
562 head.headers->AddHeader(
563 base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType,
564 head.mime_type.c_str()));
565 }
Marijn Kruisselbrink9ebd7ba2018-06-11 23:18:04566 client->OnReceiveResponse(head);
Ken Rockot314714c2017-11-05 23:36:24567 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
568 client_ = std::move(client);
569
570 if (total_bytes_to_send == 0) {
571 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42572 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24573 return;
574 }
575
576 // In case of a range request, seek to the appropriate position before
577 // sending the remaining bytes asynchronously. Under normal conditions
578 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42579 int new_position = file.Seek(base::File::FROM_BEGIN,
580 static_cast<int64_t>(first_byte_to_send));
581 if (observer)
582 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24583
584 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42585 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24586 data_producer_->WriteFromFile(
587 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42588 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
589 nullptr));
Ken Rockot314714c2017-11-05 23:36:24590 }
591
592 void OnConnectionError() {
593 binding_.Close();
594 MaybeDeleteSelf();
595 }
596
597 void MaybeDeleteSelf() {
598 if (!binding_.is_bound() && !client_.is_bound())
599 delete this;
600 }
601
Chris Mumfordb5df8ef22017-12-20 17:47:42602 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
603 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34604 // All the data has been written now. Close the data pipe. The consumer will
605 // be notified that there will be no more data to read from now.
606 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42607 if (observer)
608 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34609
Ken Rockot314714c2017-11-05 23:36:24610 if (result == MOJO_RESULT_OK)
Takashi Toyoshimaaa278662017-11-20 11:11:26611 client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
Ken Rockot314714c2017-11-05 23:36:24612 else
Takashi Toyoshimaaa278662017-11-20 11:11:26613 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24614 client_.reset();
615 MaybeDeleteSelf();
616 }
617
618 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
John Abd-El-Malekb165dc52018-01-18 17:12:18619 mojo::Binding<network::mojom::URLLoader> binding_;
620 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24621
622 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
623};
624
625} // namespace
626
627FileURLLoaderFactory::FileURLLoaderFactory(
628 const base::FilePath& profile_path,
629 scoped_refptr<base::SequencedTaskRunner> task_runner)
630 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
631
632FileURLLoaderFactory::~FileURLLoaderFactory() = default;
633
Ken Rockot314714c2017-11-05 23:36:24634void FileURLLoaderFactory::CreateLoaderAndStart(
John Abd-El-Malekb165dc52018-01-18 17:12:18635 network::mojom::URLLoaderRequest loader,
Ken Rockot314714c2017-11-05 23:36:24636 int32_t routing_id,
637 int32_t request_id,
638 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45639 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18640 network::mojom::URLLoaderClientPtr client,
Ken Rockot314714c2017-11-05 23:36:24641 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
642 base::FilePath file_path;
643 const bool is_file = net::FileURLToFilePath(request.url, &file_path);
644 if (is_file && file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
645 task_runner_->PostTask(
646 FROM_HERE,
647 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42648 request, std::move(loader), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57649 std::unique_ptr<FileURLLoaderObserver>(), nullptr));
Ken Rockot314714c2017-11-05 23:36:24650 } else {
651 task_runner_->PostTask(
652 FROM_HERE,
653 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32654 std::move(loader), client.PassInterface(),
655 DirectoryLoadingPolicy::kRespondWithListing,
656 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42657 LinkFollowingPolicy::kFollow,
Ken Rockota0dfaca12018-02-15 07:26:25658 std::unique_ptr<FileURLLoaderObserver>(),
659 nullptr /* extra_response_headers */));
Ken Rockot314714c2017-11-05 23:36:24660 }
661}
662
John Abd-El-Malekb165dc52018-01-18 17:12:18663void FileURLLoaderFactory::Clone(
664 network::mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32665 bindings_.AddBinding(this, std::move(loader));
666}
667
Ken Rockota0dfaca12018-02-15 07:26:25668void CreateFileURLLoader(
669 const network::ResourceRequest& request,
670 network::mojom::URLLoaderRequest loader,
671 network::mojom::URLLoaderClientPtr client,
672 std::unique_ptr<FileURLLoaderObserver> observer,
673 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot6414c4d92017-11-08 19:58:32674 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
Gabriel Charetteb10aeeb2018-07-26 20:15:00675 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
Ken Rockot6414c4d92017-11-08 19:58:32676 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
677 task_runner->PostTask(
678 FROM_HERE,
679 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
680 std::move(loader), client.PassInterface(),
681 DirectoryLoadingPolicy::kFail,
682 FileAccessPolicy::kUnrestricted,
Ken Rockota0dfaca12018-02-15 07:26:25683 LinkFollowingPolicy::kDoNotFollow, std::move(observer),
684 std::move(extra_response_headers)));
Ken Rockot314714c2017-11-05 23:36:24685}
686
Antonio Gomese1a02192018-08-01 15:39:44687std::unique_ptr<network::mojom::URLLoaderFactory> CreateFileURLLoaderFactory(
688 const base::FilePath& profile_path) {
689 return std::make_unique<content::FileURLLoaderFactory>(
690 profile_path, base::CreateSequencedTaskRunnerWithTraits(
691 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
692 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
693}
694
Ken Rockot314714c2017-11-05 23:36:24695} // namespace content