blob: ea4d8eee97722193b828ed3e540fd679b62b5893 [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"
Gabriel Charette44db1422018-08-06 11:19:3322#include "base/task/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
Chris Mumford90b1a0d2018-09-26 20:10:4389GURL AppendUrlSeparator(const GURL& url) {
90 std::string new_path = url.path() + '/';
91 GURL::Replacements replacements;
92 replacements.SetPathStr(new_path);
93 return url.ReplaceComponents(replacements);
94}
95
Ken Rockot314714c2017-11-05 23:36:2496class FileURLDirectoryLoader
John Abd-El-Malekb165dc52018-01-18 17:12:1897 : public network::mojom::URLLoader,
Ken Rockot314714c2017-11-05 23:36:2498 public net::DirectoryLister::DirectoryListerDelegate {
99 public:
Chris Mumford8f812662018-02-22 00:27:57100 static void CreateAndStart(
101 const base::FilePath& profile_path,
102 const network::ResourceRequest& request,
103 network::mojom::URLLoaderRequest loader,
104 network::mojom::URLLoaderClientPtrInfo client_info,
105 std::unique_ptr<FileURLLoaderObserver> observer,
106 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24107 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
108 // bindings are alive - essentially until either the client gives up or all
109 // file data has been sent to it.
110 auto* file_url_loader = new FileURLDirectoryLoader;
111 file_url_loader->Start(profile_path, request, std::move(loader),
Chris Mumford8f812662018-02-22 00:27:57112 std::move(client_info), std::move(observer),
113 std::move(response_headers));
Ken Rockot314714c2017-11-05 23:36:24114 }
115
John Abd-El-Malekb165dc52018-01-18 17:12:18116 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20117 void FollowRedirect(const base::Optional<std::vector<std::string>>&
118 to_be_removed_request_headers,
119 const base::Optional<net::HttpRequestHeaders>&
Chong Zhangf19dde92018-05-23 04:33:59120 modified_request_headers) override {}
arthursonzogni2e1524a72017-12-18 16:53:26121 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24122 void SetPriority(net::RequestPriority priority,
123 int32_t intra_priority_value) override {}
124 void PauseReadingBodyFromNet() override {}
125 void ResumeReadingBodyFromNet() override {}
126
127 private:
128 FileURLDirectoryLoader() : binding_(this) {}
129 ~FileURLDirectoryLoader() override = default;
130
131 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45132 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18133 network::mojom::URLLoaderRequest loader,
134 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumford8f812662018-02-22 00:27:57135 std::unique_ptr<content::FileURLLoaderObserver> observer,
136 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24137 binding_.Bind(std::move(loader));
138 binding_.set_connection_error_handler(base::BindOnce(
139 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
140
John Abd-El-Malekb165dc52018-01-18 17:12:18141 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24142 client.Bind(std::move(client_info));
143
144 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26145 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24146 return;
147 }
148
149 base::File::Info info;
150 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26151 client->OnComplete(
152 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24153 return;
154 }
155
156 if (!GetContentClient()->browser()->IsFileAccessAllowed(
157 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26158 client->OnComplete(
159 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24160 return;
161 }
162
163 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
164 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26165 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24166 return;
167 }
168
John Abd-El-Malek46248032018-01-17 19:11:23169 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24170 head.mime_type = "text/html";
171 head.charset = "utf-8";
Marijn Kruisselbrink9ebd7ba2018-06-11 23:18:04172 client->OnReceiveResponse(head);
Ken Rockot314714c2017-11-05 23:36:24173 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
174 client_ = std::move(client);
175
176 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
177 lister_->Start();
178
179 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
180 std::move(pipe.producer_handle));
181 }
182
183 void OnConnectionError() {
184 binding_.Close();
185 MaybeDeleteSelf();
186 }
187
188 void MaybeDeleteSelf() {
189 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
190 delete this;
191 }
192
193 // net::DirectoryLister::DirectoryListerDelegate:
194 void OnListFile(
195 const net::DirectoryLister::DirectoryListerData& data) override {
196 if (!wrote_header_) {
197 wrote_header_ = true;
198
199#if defined(OS_WIN)
200 const base::string16& title = path_.value();
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48201#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24202 const base::string16& title =
203 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
204#endif
205 pending_data_.append(net::GetDirectoryListingHeader(title));
206
207 // If not a top-level directory, add a link to the parent directory. To
208 // figure this out, first normalize |path_| by stripping trailing
209 // separators. Then compare the result to its DirName(). For the top-level
210 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
211 // DirName().
212 base::FilePath stripped_path = path_.StripTrailingSeparators();
213 if (stripped_path != stripped_path.DirName())
214 pending_data_.append(net::GetParentDirectoryLink());
215 }
216
217 // Skip current / parent links from the listing.
218 base::FilePath filename = data.info.GetName();
219 if (filename.value() != base::FilePath::kCurrentDirectory &&
220 filename.value() != base::FilePath::kParentDirectory) {
221#if defined(OS_WIN)
222 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48223#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24224 const std::string& raw_bytes = filename.value();
225#endif
226 pending_data_.append(net::GetDirectoryListingEntry(
227 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
228 data.info.GetSize(), data.info.GetLastModifiedTime()));
229 }
230
231 MaybeTransferPendingData();
232 }
233
234 void OnListDone(int error) override {
235 listing_result_ = error;
236 lister_.reset();
237 MaybeDeleteSelf();
238 }
239
240 void MaybeTransferPendingData() {
241 if (transfer_in_progress_)
242 return;
243
244 transfer_in_progress_ = true;
245 data_producer_->Write(pending_data_,
Daniel Murphy0ecba612018-02-07 20:56:18246 mojo::StringDataPipeProducer::AsyncWritingMode::
247 STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
Ken Rockot314714c2017-11-05 23:36:24248 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
249 base::Unretained(this)));
250 // The producer above will have already copied any parts of |pending_data_|
251 // that couldn't be written immediately, so we can wipe it out here to begin
252 // accumulating more data.
253 pending_data_.clear();
254 }
255
256 void OnDataWritten(MojoResult result) {
257 transfer_in_progress_ = false;
258
259 int completion_status;
260 if (result == MOJO_RESULT_OK) {
261 if (!pending_data_.empty()) {
262 // Keep flushing the data buffer as long as it's non-empty and pipe
263 // writes are succeeding.
264 MaybeTransferPendingData();
265 return;
266 }
267
268 // If there's no pending data but the lister is still active, we simply
269 // wait for more listing results.
270 if (lister_)
271 return;
272
273 // At this point we know the listing is complete and all available data
274 // has been transferred. We inherit the status of the listing operation.
275 completion_status = listing_result_;
276 } else {
277 completion_status = net::ERR_FAILED;
278 }
279
arthursonzogni3a4ca9f2017-12-07 17:58:34280 // All the data has been written now. Close the data pipe. The consumer will
281 // be notified that there will be no more data to read from now.
282 data_producer_.reset();
283
Takashi Toyoshimaaa278662017-11-20 11:11:26284 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24285 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34286
Ken Rockot314714c2017-11-05 23:36:24287 MaybeDeleteSelf();
288 }
289
290 base::FilePath path_;
291 std::unique_ptr<net::DirectoryLister> lister_;
292 bool wrote_header_ = false;
293 int listing_result_;
294
John Abd-El-Malekb165dc52018-01-18 17:12:18295 mojo::Binding<network::mojom::URLLoader> binding_;
296 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24297
298 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
299 std::string pending_data_;
300 bool transfer_in_progress_ = false;
301
302 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
303};
304
John Abd-El-Malekb165dc52018-01-18 17:12:18305class FileURLLoader : public network::mojom::URLLoader {
Ken Rockot314714c2017-11-05 23:36:24306 public:
Ken Rockota0dfaca12018-02-15 07:26:25307 static void CreateAndStart(
308 const base::FilePath& profile_path,
309 const network::ResourceRequest& request,
310 network::mojom::URLLoaderRequest loader,
311 network::mojom::URLLoaderClientPtrInfo client_info,
312 DirectoryLoadingPolicy directory_loading_policy,
313 FileAccessPolicy file_access_policy,
314 LinkFollowingPolicy link_following_policy,
315 std::unique_ptr<FileURLLoaderObserver> observer,
316 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot314714c2017-11-05 23:36:24317 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
318 // bindings are alive - essentially until either the client gives up or all
319 // file data has been sent to it.
320 auto* file_url_loader = new FileURLLoader;
Ken Rockota0dfaca12018-02-15 07:26:25321 file_url_loader->Start(
322 profile_path, request, std::move(loader), std::move(client_info),
323 directory_loading_policy, file_access_policy, link_following_policy,
324 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24325 }
326
John Abd-El-Malekb165dc52018-01-18 17:12:18327 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20328 void FollowRedirect(const base::Optional<std::vector<std::string>>&
329 to_be_removed_request_headers,
330 const base::Optional<net::HttpRequestHeaders>&
Chris Mumford90b1a0d2018-09-26 20:10:43331 modified_request_headers) override {
332 std::unique_ptr<RedirectData> redirect_data = std::move(redirect_data_);
333 if (redirect_data->is_directory) {
334 FileURLDirectoryLoader::CreateAndStart(
335 redirect_data->profile_path, redirect_data->request,
336 binding_.Unbind(), client_.PassInterface(),
337 std::move(redirect_data->observer),
338 std::move(redirect_data->extra_response_headers));
339 } else {
340 FileURLLoader::CreateAndStart(
341 redirect_data->profile_path, redirect_data->request,
342 binding_.Unbind(), client_.PassInterface(),
343 redirect_data->directory_loading_policy,
344 redirect_data->file_access_policy,
345 redirect_data->link_following_policy,
346 std::move(redirect_data->observer),
347 std::move(redirect_data->extra_response_headers));
348 }
349 MaybeDeleteSelf();
350 }
arthursonzogni2e1524a72017-12-18 16:53:26351 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24352 void SetPriority(net::RequestPriority priority,
353 int32_t intra_priority_value) override {}
354 void PauseReadingBodyFromNet() override {}
355 void ResumeReadingBodyFromNet() override {}
356
357 private:
Chris Mumford90b1a0d2018-09-26 20:10:43358 // Used to save outstanding redirect data while waiting for FollowRedirect
359 // to be called. Values default to their most restrictive in case they are
360 // not set.
361 struct RedirectData {
362 bool is_directory = false;
363 base::FilePath profile_path;
364 network::ResourceRequest request;
365 network::mojom::URLLoaderRequest loader;
366 DirectoryLoadingPolicy directory_loading_policy =
367 DirectoryLoadingPolicy::kFail;
368 FileAccessPolicy file_access_policy = FileAccessPolicy::kRestricted;
369 LinkFollowingPolicy link_following_policy =
370 LinkFollowingPolicy::kDoNotFollow;
371 std::unique_ptr<FileURLLoaderObserver> observer;
372 scoped_refptr<net::HttpResponseHeaders> extra_response_headers;
373 };
374
Ken Rockot314714c2017-11-05 23:36:24375 FileURLLoader() : binding_(this) {}
376 ~FileURLLoader() override = default;
377
378 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45379 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18380 network::mojom::URLLoaderRequest loader,
381 network::mojom::URLLoaderClientPtrInfo client_info,
Ken Rockot6414c4d92017-11-08 19:58:32382 DirectoryLoadingPolicy directory_loading_policy,
383 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42384 LinkFollowingPolicy link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25385 std::unique_ptr<FileURLLoaderObserver> observer,
386 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
John Abd-El-Malek46248032018-01-17 19:11:23387 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24388 head.request_start = base::TimeTicks::Now();
389 head.response_start = base::TimeTicks::Now();
Ken Rockota0dfaca12018-02-15 07:26:25390 head.headers = extra_response_headers;
Ken Rockot314714c2017-11-05 23:36:24391 binding_.Bind(std::move(loader));
392 binding_.set_connection_error_handler(base::BindOnce(
393 &FileURLLoader::OnConnectionError, base::Unretained(this)));
394
Chris Mumford90b1a0d2018-09-26 20:10:43395 client_.Bind(std::move(client_info));
Ken Rockot314714c2017-11-05 23:36:24396
397 base::FilePath path;
398 if (!net::FileURLToFilePath(request.url, &path)) {
Chris Mumford90b1a0d2018-09-26 20:10:43399 OnClientComplete(net::ERR_FAILED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24400 return;
401 }
402
403 base::File::Info info;
404 if (!base::GetFileInfo(path, &info)) {
Chris Mumford90b1a0d2018-09-26 20:10:43405 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24406 return;
407 }
408
Ken Rockot6414c4d92017-11-08 19:58:32409 if (info.is_directory) {
410 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Chris Mumford90b1a0d2018-09-26 20:10:43411 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
Ken Rockot6414c4d92017-11-08 19:58:32412 return;
413 }
Ken Rockot314714c2017-11-05 23:36:24414
Ken Rockot6414c4d92017-11-08 19:58:32415 DCHECK_EQ(directory_loading_policy,
416 DirectoryLoadingPolicy::kRespondWithListing);
417
Chris Mumford90b1a0d2018-09-26 20:10:43418 net::RedirectInfo redirect_info;
419 redirect_info.new_method = "GET";
420 redirect_info.status_code = 301;
421 redirect_info.new_url = path.EndsWithSeparator()
422 ? request.url
423 : AppendUrlSeparator(request.url);
424 head.encoded_data_length = 0;
Ken Rockot6414c4d92017-11-08 19:58:32425
Chris Mumford90b1a0d2018-09-26 20:10:43426 redirect_data_ = std::make_unique<RedirectData>();
427 redirect_data_->is_directory = true;
428 redirect_data_->profile_path = std::move(profile_path);
429 redirect_data_->request = request;
430 redirect_data_->directory_loading_policy = directory_loading_policy;
431 redirect_data_->file_access_policy = file_access_policy;
432 redirect_data_->link_following_policy = link_following_policy;
433 redirect_data_->request.url = redirect_info.new_url;
434 redirect_data_->observer = std::move(observer);
435 redirect_data_->extra_response_headers =
436 std::move(extra_response_headers);
Ken Rockot314714c2017-11-05 23:36:24437
Chris Mumford90b1a0d2018-09-26 20:10:43438 client_->OnReceiveRedirect(redirect_info, head);
Ken Rockot314714c2017-11-05 23:36:24439 return;
440 }
441
442#if defined(OS_WIN)
443 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32444 if (link_following_policy == LinkFollowingPolicy::kFollow &&
445 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24446 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
447 // Follow Windows shortcuts
Chris Mumford90b1a0d2018-09-26 20:10:43448 redirect_data_ = std::make_unique<RedirectData>();
449 if (!base::GetFileInfo(shortcut_target, &info)) {
450 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
451 return;
452 }
453
Ken Rockot314714c2017-11-05 23:36:24454 GURL new_url = net::FilePathToFileURL(shortcut_target);
Chris Mumford90b1a0d2018-09-26 20:10:43455 if (info.is_directory && !path.EndsWithSeparator())
456 new_url = AppendUrlSeparator(new_url);
Ken Rockot314714c2017-11-05 23:36:24457
458 net::RedirectInfo redirect_info;
459 redirect_info.new_method = "GET";
460 redirect_info.status_code = 301;
461 redirect_info.new_url = new_url;
462 head.encoded_data_length = 0;
Ken Rockot314714c2017-11-05 23:36:24463
Chris Mumford90b1a0d2018-09-26 20:10:43464 redirect_data_->is_directory = info.is_directory;
465 redirect_data_->profile_path = std::move(profile_path);
466 redirect_data_->request = request;
467 redirect_data_->directory_loading_policy = directory_loading_policy;
468 redirect_data_->file_access_policy = file_access_policy;
469 redirect_data_->link_following_policy = link_following_policy;
470 redirect_data_->request.url = redirect_info.new_url;
471 redirect_data_->observer = std::move(observer);
472 redirect_data_->extra_response_headers =
473 std::move(extra_response_headers);
474
475 client_->OnReceiveRedirect(redirect_info, head);
476 return;
Ken Rockot314714c2017-11-05 23:36:24477 }
478#endif // defined(OS_WIN)
479
Ken Rockot6414c4d92017-11-08 19:58:32480 if (file_access_policy == FileAccessPolicy::kRestricted &&
481 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24482 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Chris Mumford90b1a0d2018-09-26 20:10:43483 OnClientComplete(net::ERR_ACCESS_DENIED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24484 return;
485 }
486
487 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
488 if (!pipe.consumer_handle.is_valid()) {
Chris Mumford90b1a0d2018-09-26 20:10:43489 OnClientComplete(net::ERR_FAILED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24490 return;
491 }
492
493 // Should never be possible for this to be a directory. If FileURLLoader
494 // is used to respond to a directory request, it must be because the URL
495 // path didn't have a trailing path separator. In that case we finish with
496 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
497 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42498 if (observer)
499 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24500
501 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumford8f812662018-02-22 00:27:57502 if (!file.IsValid()) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10503 if (observer) {
504 observer->OnBytesRead(nullptr, 0u, file.error_details());
Chris Mumford8f812662018-02-22 00:27:57505 observer->OnDoneReading();
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10506 }
507 net::Error net_error = net::FileErrorToNetError(file.error_details());
Chris Mumford90b1a0d2018-09-26 20:10:43508 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
509 client_.reset();
Chris Mumford8f812662018-02-22 00:27:57510 MaybeDeleteSelf();
511 return;
512 }
Ken Rockot314714c2017-11-05 23:36:24513 char initial_read_buffer[net::kMaxBytesToSniff];
514 int initial_read_result =
515 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
516 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42517 base::File::Error read_error = base::File::GetLastFileError();
518 DCHECK_NE(base::File::FILE_OK, read_error);
519 if (observer) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10520 // This can happen when the file is unreadable (which can happen during
521 // corruption). We need to be sure to inform
522 // the observer that we've finished reading so that it can proceed.
Chris Mumfordb5df8ef22017-12-20 17:47:42523 observer->OnBytesRead(nullptr, 0u, read_error);
524 observer->OnDoneReading();
525 }
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10526 net::Error net_error = net::FileErrorToNetError(read_error);
Chris Mumford90b1a0d2018-09-26 20:10:43527 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
528 client_.reset();
529 MaybeDeleteSelf();
Ken Rockot314714c2017-11-05 23:36:24530 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42531 } else if (observer) {
532 observer->OnBytesRead(initial_read_buffer, initial_read_result,
533 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24534 }
535 size_t initial_read_size = static_cast<size_t>(initial_read_result);
536
537 std::string range_header;
538 net::HttpByteRange byte_range;
539 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
540 &range_header)) {
541 // Handle a simple Range header for a single range.
542 std::vector<net::HttpByteRange> ranges;
543 bool fail = false;
544 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
545 ranges.size() == 1) {
546 byte_range = ranges[0];
547 if (!byte_range.ComputeBounds(info.size))
548 fail = true;
549 } else {
550 fail = true;
551 }
552
553 if (fail) {
Chris Mumford90b1a0d2018-09-26 20:10:43554 OnClientComplete(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
555 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24556 return;
557 }
558 }
559
560 size_t first_byte_to_send = 0;
561 size_t total_bytes_to_send = static_cast<size_t>(info.size);
Antonio Gomes402dbf52018-08-02 11:06:41562
563 total_bytes_written_ = static_cast<size_t>(info.size);
564
Ken Rockot314714c2017-11-05 23:36:24565 if (byte_range.IsValid()) {
566 first_byte_to_send =
567 static_cast<size_t>(byte_range.first_byte_position());
568 total_bytes_to_send =
569 static_cast<size_t>(byte_range.last_byte_position()) -
570 first_byte_to_send + 1;
571 }
572
573 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
574
575 if (first_byte_to_send < initial_read_size) {
576 // Write any data we read for MIME sniffing, constraining by range where
577 // applicable. This will always fit in the pipe (see assertion near
578 // |kDefaultFileUrlPipeSize| definition).
579 uint32_t write_size = std::min(
580 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
581 static_cast<uint32_t>(total_bytes_to_send));
582 const uint32_t expected_write_size = write_size;
583 MojoResult result = pipe.producer_handle->WriteData(
584 &initial_read_buffer[first_byte_to_send], &write_size,
585 MOJO_WRITE_DATA_FLAG_NONE);
586 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42587 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24588 return;
589 }
590
591 // Discount the bytes we just sent from the total range.
592 first_byte_to_send = initial_read_size;
593 total_bytes_to_send -= write_size;
594 }
595
596 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
Matt Menkefcbb1bd72018-01-31 21:53:12597 net::SniffMimeType(
598 initial_read_buffer, initial_read_result, request.url, head.mime_type,
599 GetContentClient()->browser()->ForceSniffingFileUrlsForHtml()
600 ? net::ForceSniffFileUrlsForHtml::kEnabled
601 : net::ForceSniffFileUrlsForHtml::kDisabled,
602 &head.mime_type);
Makoto Shimazu21950e82018-08-02 12:37:14603 head.did_mime_sniff = true;
Ken Rockot314714c2017-11-05 23:36:24604 }
Chris Mumford8f812662018-02-22 00:27:57605 if (head.headers) {
606 head.headers->AddHeader(
607 base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType,
608 head.mime_type.c_str()));
609 }
Chris Mumford90b1a0d2018-09-26 20:10:43610 client_->OnReceiveResponse(head);
611 client_->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
Ken Rockot314714c2017-11-05 23:36:24612
613 if (total_bytes_to_send == 0) {
614 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42615 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24616 return;
617 }
618
619 // In case of a range request, seek to the appropriate position before
620 // sending the remaining bytes asynchronously. Under normal conditions
621 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42622 int new_position = file.Seek(base::File::FROM_BEGIN,
623 static_cast<int64_t>(first_byte_to_send));
624 if (observer)
625 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24626
627 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42628 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24629 data_producer_->WriteFromFile(
630 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42631 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
632 nullptr));
Ken Rockot314714c2017-11-05 23:36:24633 }
634
635 void OnConnectionError() {
636 binding_.Close();
637 MaybeDeleteSelf();
638 }
639
Chris Mumford90b1a0d2018-09-26 20:10:43640 void OnClientComplete(net::Error net_error,
641 std::unique_ptr<FileURLLoaderObserver> observer) {
642 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
643 client_.reset();
644 if (observer)
645 observer->OnDoneReading();
646 MaybeDeleteSelf();
647 }
648
Ken Rockot314714c2017-11-05 23:36:24649 void MaybeDeleteSelf() {
650 if (!binding_.is_bound() && !client_.is_bound())
651 delete this;
652 }
653
Chris Mumfordb5df8ef22017-12-20 17:47:42654 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
655 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34656 // All the data has been written now. Close the data pipe. The consumer will
657 // be notified that there will be no more data to read from now.
658 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42659 if (observer)
660 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34661
Antonio Gomes402dbf52018-08-02 11:06:41662 if (result == MOJO_RESULT_OK) {
663 network::URLLoaderCompletionStatus status(net::OK);
664 status.encoded_data_length = total_bytes_written_;
665 status.encoded_body_length = total_bytes_written_;
666 status.decoded_body_length = total_bytes_written_;
667 client_->OnComplete(status);
668 } else {
Takashi Toyoshimaaa278662017-11-20 11:11:26669 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Antonio Gomes402dbf52018-08-02 11:06:41670 }
Ken Rockot314714c2017-11-05 23:36:24671 client_.reset();
672 MaybeDeleteSelf();
673 }
674
675 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
John Abd-El-Malekb165dc52018-01-18 17:12:18676 mojo::Binding<network::mojom::URLLoader> binding_;
677 network::mojom::URLLoaderClientPtr client_;
Chris Mumford90b1a0d2018-09-26 20:10:43678 std::unique_ptr<RedirectData> redirect_data_;
Ken Rockot314714c2017-11-05 23:36:24679
Antonio Gomes402dbf52018-08-02 11:06:41680 // In case of successful loads, this holds the total of bytes written.
681 // It is used to set some of the URLLoaderCompletionStatus data passed back
682 // to the URLLoaderClients (eg SimpleURLLoader).
683 size_t total_bytes_written_ = 0;
684
Ken Rockot314714c2017-11-05 23:36:24685 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
686};
687
688} // namespace
689
690FileURLLoaderFactory::FileURLLoaderFactory(
691 const base::FilePath& profile_path,
692 scoped_refptr<base::SequencedTaskRunner> task_runner)
693 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
694
695FileURLLoaderFactory::~FileURLLoaderFactory() = default;
696
Ken Rockot314714c2017-11-05 23:36:24697void FileURLLoaderFactory::CreateLoaderAndStart(
John Abd-El-Malekb165dc52018-01-18 17:12:18698 network::mojom::URLLoaderRequest loader,
Ken Rockot314714c2017-11-05 23:36:24699 int32_t routing_id,
700 int32_t request_id,
701 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45702 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18703 network::mojom::URLLoaderClientPtr client,
Ken Rockot314714c2017-11-05 23:36:24704 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
705 base::FilePath file_path;
Chris Mumford90b1a0d2018-09-26 20:10:43706 if (!net::FileURLToFilePath(request.url, &file_path)) {
707 client->OnComplete(
708 network::URLLoaderCompletionStatus(net::ERR_INVALID_URL));
709 return;
710 }
711 if (file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
Ken Rockot314714c2017-11-05 23:36:24712 task_runner_->PostTask(
713 FROM_HERE,
714 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42715 request, std::move(loader), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57716 std::unique_ptr<FileURLLoaderObserver>(), nullptr));
Ken Rockot314714c2017-11-05 23:36:24717 } else {
718 task_runner_->PostTask(
719 FROM_HERE,
720 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32721 std::move(loader), client.PassInterface(),
722 DirectoryLoadingPolicy::kRespondWithListing,
723 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42724 LinkFollowingPolicy::kFollow,
Ken Rockota0dfaca12018-02-15 07:26:25725 std::unique_ptr<FileURLLoaderObserver>(),
726 nullptr /* extra_response_headers */));
Ken Rockot314714c2017-11-05 23:36:24727 }
728}
729
John Abd-El-Malekb165dc52018-01-18 17:12:18730void FileURLLoaderFactory::Clone(
731 network::mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32732 bindings_.AddBinding(this, std::move(loader));
733}
734
Ken Rockota0dfaca12018-02-15 07:26:25735void CreateFileURLLoader(
736 const network::ResourceRequest& request,
737 network::mojom::URLLoaderRequest loader,
738 network::mojom::URLLoaderClientPtr client,
739 std::unique_ptr<FileURLLoaderObserver> observer,
740 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot6414c4d92017-11-08 19:58:32741 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
Gabriel Charetteb10aeeb2018-07-26 20:15:00742 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
Ken Rockot6414c4d92017-11-08 19:58:32743 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
744 task_runner->PostTask(
745 FROM_HERE,
746 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
747 std::move(loader), client.PassInterface(),
748 DirectoryLoadingPolicy::kFail,
749 FileAccessPolicy::kUnrestricted,
Ken Rockota0dfaca12018-02-15 07:26:25750 LinkFollowingPolicy::kDoNotFollow, std::move(observer),
751 std::move(extra_response_headers)));
Ken Rockot314714c2017-11-05 23:36:24752}
753
Antonio Gomese1a02192018-08-01 15:39:44754std::unique_ptr<network::mojom::URLLoaderFactory> CreateFileURLLoaderFactory(
755 const base::FilePath& profile_path) {
756 return std::make_unique<content::FileURLLoaderFactory>(
757 profile_path, base::CreateSequencedTaskRunnerWithTraits(
758 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
759 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
760}
761
Ken Rockot314714c2017-11-05 23:36:24762} // namespace content