blob: 389fd1962aed6ac91dc3b02d2011f824177b4d68 [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"
Yutaka Hirano6eddf7382018-10-10 06:44:0428#include "content/public/common/content_switches.h"
Ken Rockot314714c2017-11-05 23:36:2429#include "mojo/public/cpp/bindings/strong_binding.h"
30#include "mojo/public/cpp/system/data_pipe.h"
31#include "mojo/public/cpp/system/file_data_pipe_producer.h"
32#include "mojo/public/cpp/system/string_data_pipe_producer.h"
33#include "net/base/directory_lister.h"
34#include "net/base/directory_listing.h"
35#include "net/base/filename_util.h"
36#include "net/base/mime_sniffer.h"
37#include "net/base/mime_util.h"
38#include "net/base/net_errors.h"
39#include "net/http/http_byte_range.h"
40#include "net/http/http_util.h"
41#include "net/url_request/redirect_info.h"
Yutaka Hirano6eddf7382018-10-10 06:44:0442#include "services/network/public/cpp/cors/cors_error_status.h"
John Abd-El-Malek1df61792018-01-12 20:40:4543#include "services/network/public/cpp/resource_request.h"
Yutaka Hirano6eddf7382018-10-10 06:44:0444#include "services/network/public/mojom/cors.mojom-shared.h"
Ken Rockot54311e62018-02-10 19:01:5245#include "services/network/public/mojom/url_loader.mojom.h"
Chris Mumfordb5df8ef22017-12-20 17:47:4246#include "storage/common/fileapi/file_system_util.h"
Ken Rockot314714c2017-11-05 23:36:2447#include "url/gurl.h"
48
49#if defined(OS_WIN)
50#include "base/win/shortcut.h"
51#endif
52
53namespace content {
54namespace {
55
56constexpr size_t kDefaultFileUrlPipeSize = 65536;
57
58// Because this makes things simpler.
59static_assert(kDefaultFileUrlPipeSize >= net::kMaxBytesToSniff,
60 "Default file data pipe size must be at least as large as a MIME-"
61 "type sniffing buffer.");
62
Ken Rockot6414c4d92017-11-08 19:58:3263// Policy to control how a FileURLLoader will handle directory URLs.
64enum class DirectoryLoadingPolicy {
65 // File paths which refer to directories are allowed and will load as an
66 // HTML directory listing.
67 kRespondWithListing,
68
69 // File paths which refer to directories are treated as non-existent and
70 // will result in FILE_NOT_FOUND errors.
71 kFail,
72};
73
74// Policy to control whether or not file access constraints imposed by content
75// or its embedder should be honored by a FileURLLoader.
76enum class FileAccessPolicy {
77 // Enforces file acccess policy defined by content and/or its embedder.
78 kRestricted,
79
80 // Ignores file access policy, allowing contents to be loaded from any
81 // resolvable file path given.
82 kUnrestricted,
83};
84
85// Policy to control whether or not a FileURLLoader should follow filesystem
86// links (e.g. Windows shortcuts) where applicable.
87enum class LinkFollowingPolicy {
88 kFollow,
89 kDoNotFollow,
90};
91
Chris Mumford90b1a0d2018-09-26 20:10:4392GURL AppendUrlSeparator(const GURL& url) {
93 std::string new_path = url.path() + '/';
94 GURL::Replacements replacements;
95 replacements.SetPathStr(new_path);
96 return url.ReplaceComponents(replacements);
97}
98
Yutaka Hirano6eddf7382018-10-10 06:44:0499bool ShouldFailRequestDueToCORS(const network::ResourceRequest& request) {
100 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
101 switches::kDisableWebSecurity)) {
102 return false;
103 }
104
105 const auto mode = request.fetch_request_mode;
106 if (mode == network::mojom::FetchRequestMode::kNavigate ||
107 mode == network::mojom::FetchRequestMode::kNoCORS) {
108 return false;
109 }
110
111 if (!request.request_initiator)
112 return true;
113
114 return !request.request_initiator->IsSameOriginWith(
115 url::Origin::Create(request.url));
116}
117
Ken Rockot314714c2017-11-05 23:36:24118class FileURLDirectoryLoader
John Abd-El-Malekb165dc52018-01-18 17:12:18119 : public network::mojom::URLLoader,
Ken Rockot314714c2017-11-05 23:36:24120 public net::DirectoryLister::DirectoryListerDelegate {
121 public:
Chris Mumford8f812662018-02-22 00:27:57122 static void CreateAndStart(
123 const base::FilePath& profile_path,
124 const network::ResourceRequest& request,
125 network::mojom::URLLoaderRequest loader,
126 network::mojom::URLLoaderClientPtrInfo client_info,
127 std::unique_ptr<FileURLLoaderObserver> observer,
128 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24129 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
130 // bindings are alive - essentially until either the client gives up or all
131 // file data has been sent to it.
132 auto* file_url_loader = new FileURLDirectoryLoader;
133 file_url_loader->Start(profile_path, request, std::move(loader),
Chris Mumford8f812662018-02-22 00:27:57134 std::move(client_info), std::move(observer),
135 std::move(response_headers));
Ken Rockot314714c2017-11-05 23:36:24136 }
137
John Abd-El-Malekb165dc52018-01-18 17:12:18138 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20139 void FollowRedirect(const base::Optional<std::vector<std::string>>&
140 to_be_removed_request_headers,
141 const base::Optional<net::HttpRequestHeaders>&
Chong Zhangf19dde92018-05-23 04:33:59142 modified_request_headers) override {}
arthursonzogni2e1524a72017-12-18 16:53:26143 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24144 void SetPriority(net::RequestPriority priority,
145 int32_t intra_priority_value) override {}
146 void PauseReadingBodyFromNet() override {}
147 void ResumeReadingBodyFromNet() override {}
148
149 private:
150 FileURLDirectoryLoader() : binding_(this) {}
151 ~FileURLDirectoryLoader() override = default;
152
153 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45154 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18155 network::mojom::URLLoaderRequest loader,
156 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumford8f812662018-02-22 00:27:57157 std::unique_ptr<content::FileURLLoaderObserver> observer,
158 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24159 binding_.Bind(std::move(loader));
160 binding_.set_connection_error_handler(base::BindOnce(
161 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
162
John Abd-El-Malekb165dc52018-01-18 17:12:18163 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24164 client.Bind(std::move(client_info));
165
166 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26167 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24168 return;
169 }
170
171 base::File::Info info;
172 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26173 client->OnComplete(
174 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24175 return;
176 }
177
178 if (!GetContentClient()->browser()->IsFileAccessAllowed(
179 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26180 client->OnComplete(
181 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24182 return;
183 }
184
185 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
186 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26187 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24188 return;
189 }
190
John Abd-El-Malek46248032018-01-17 19:11:23191 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24192 head.mime_type = "text/html";
193 head.charset = "utf-8";
Marijn Kruisselbrink9ebd7ba2018-06-11 23:18:04194 client->OnReceiveResponse(head);
Ken Rockot314714c2017-11-05 23:36:24195 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
196 client_ = std::move(client);
197
198 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
199 lister_->Start();
200
201 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
202 std::move(pipe.producer_handle));
203 }
204
205 void OnConnectionError() {
206 binding_.Close();
207 MaybeDeleteSelf();
208 }
209
210 void MaybeDeleteSelf() {
211 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
212 delete this;
213 }
214
215 // net::DirectoryLister::DirectoryListerDelegate:
216 void OnListFile(
217 const net::DirectoryLister::DirectoryListerData& data) override {
218 if (!wrote_header_) {
219 wrote_header_ = true;
220
221#if defined(OS_WIN)
222 const base::string16& title = path_.value();
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48223#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24224 const base::string16& title =
225 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
226#endif
227 pending_data_.append(net::GetDirectoryListingHeader(title));
228
229 // If not a top-level directory, add a link to the parent directory. To
230 // figure this out, first normalize |path_| by stripping trailing
231 // separators. Then compare the result to its DirName(). For the top-level
232 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
233 // DirName().
234 base::FilePath stripped_path = path_.StripTrailingSeparators();
235 if (stripped_path != stripped_path.DirName())
236 pending_data_.append(net::GetParentDirectoryLink());
237 }
238
239 // Skip current / parent links from the listing.
240 base::FilePath filename = data.info.GetName();
241 if (filename.value() != base::FilePath::kCurrentDirectory &&
242 filename.value() != base::FilePath::kParentDirectory) {
243#if defined(OS_WIN)
244 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
Fabrice de Gans-Riberi91fa35052018-05-07 19:33:48245#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
Ken Rockot314714c2017-11-05 23:36:24246 const std::string& raw_bytes = filename.value();
247#endif
248 pending_data_.append(net::GetDirectoryListingEntry(
249 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
250 data.info.GetSize(), data.info.GetLastModifiedTime()));
251 }
252
253 MaybeTransferPendingData();
254 }
255
256 void OnListDone(int error) override {
257 listing_result_ = error;
258 lister_.reset();
259 MaybeDeleteSelf();
260 }
261
262 void MaybeTransferPendingData() {
263 if (transfer_in_progress_)
264 return;
265
266 transfer_in_progress_ = true;
267 data_producer_->Write(pending_data_,
Daniel Murphy0ecba612018-02-07 20:56:18268 mojo::StringDataPipeProducer::AsyncWritingMode::
269 STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
Ken Rockot314714c2017-11-05 23:36:24270 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
271 base::Unretained(this)));
272 // The producer above will have already copied any parts of |pending_data_|
273 // that couldn't be written immediately, so we can wipe it out here to begin
274 // accumulating more data.
275 pending_data_.clear();
276 }
277
278 void OnDataWritten(MojoResult result) {
279 transfer_in_progress_ = false;
280
281 int completion_status;
282 if (result == MOJO_RESULT_OK) {
283 if (!pending_data_.empty()) {
284 // Keep flushing the data buffer as long as it's non-empty and pipe
285 // writes are succeeding.
286 MaybeTransferPendingData();
287 return;
288 }
289
290 // If there's no pending data but the lister is still active, we simply
291 // wait for more listing results.
292 if (lister_)
293 return;
294
295 // At this point we know the listing is complete and all available data
296 // has been transferred. We inherit the status of the listing operation.
297 completion_status = listing_result_;
298 } else {
299 completion_status = net::ERR_FAILED;
300 }
301
arthursonzogni3a4ca9f2017-12-07 17:58:34302 // All the data has been written now. Close the data pipe. The consumer will
303 // be notified that there will be no more data to read from now.
304 data_producer_.reset();
305
Takashi Toyoshimaaa278662017-11-20 11:11:26306 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24307 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34308
Ken Rockot314714c2017-11-05 23:36:24309 MaybeDeleteSelf();
310 }
311
312 base::FilePath path_;
313 std::unique_ptr<net::DirectoryLister> lister_;
314 bool wrote_header_ = false;
315 int listing_result_;
316
John Abd-El-Malekb165dc52018-01-18 17:12:18317 mojo::Binding<network::mojom::URLLoader> binding_;
318 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24319
320 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
321 std::string pending_data_;
322 bool transfer_in_progress_ = false;
323
324 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
325};
326
John Abd-El-Malekb165dc52018-01-18 17:12:18327class FileURLLoader : public network::mojom::URLLoader {
Ken Rockot314714c2017-11-05 23:36:24328 public:
Ken Rockota0dfaca12018-02-15 07:26:25329 static void CreateAndStart(
330 const base::FilePath& profile_path,
331 const network::ResourceRequest& request,
332 network::mojom::URLLoaderRequest loader,
333 network::mojom::URLLoaderClientPtrInfo client_info,
334 DirectoryLoadingPolicy directory_loading_policy,
335 FileAccessPolicy file_access_policy,
336 LinkFollowingPolicy link_following_policy,
337 std::unique_ptr<FileURLLoaderObserver> observer,
338 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot314714c2017-11-05 23:36:24339 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
340 // bindings are alive - essentially until either the client gives up or all
341 // file data has been sent to it.
342 auto* file_url_loader = new FileURLLoader;
Ken Rockota0dfaca12018-02-15 07:26:25343 file_url_loader->Start(
344 profile_path, request, std::move(loader), std::move(client_info),
345 directory_loading_policy, file_access_policy, link_following_policy,
346 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24347 }
348
John Abd-El-Malekb165dc52018-01-18 17:12:18349 // network::mojom::URLLoader:
Jun Cai605ff0e72018-06-12 22:29:20350 void FollowRedirect(const base::Optional<std::vector<std::string>>&
351 to_be_removed_request_headers,
352 const base::Optional<net::HttpRequestHeaders>&
Chris Mumford90b1a0d2018-09-26 20:10:43353 modified_request_headers) override {
354 std::unique_ptr<RedirectData> redirect_data = std::move(redirect_data_);
355 if (redirect_data->is_directory) {
356 FileURLDirectoryLoader::CreateAndStart(
357 redirect_data->profile_path, redirect_data->request,
358 binding_.Unbind(), client_.PassInterface(),
359 std::move(redirect_data->observer),
360 std::move(redirect_data->extra_response_headers));
361 } else {
362 FileURLLoader::CreateAndStart(
363 redirect_data->profile_path, redirect_data->request,
364 binding_.Unbind(), client_.PassInterface(),
365 redirect_data->directory_loading_policy,
366 redirect_data->file_access_policy,
367 redirect_data->link_following_policy,
368 std::move(redirect_data->observer),
369 std::move(redirect_data->extra_response_headers));
370 }
371 MaybeDeleteSelf();
372 }
arthursonzogni2e1524a72017-12-18 16:53:26373 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24374 void SetPriority(net::RequestPriority priority,
375 int32_t intra_priority_value) override {}
376 void PauseReadingBodyFromNet() override {}
377 void ResumeReadingBodyFromNet() override {}
378
379 private:
Chris Mumford90b1a0d2018-09-26 20:10:43380 // Used to save outstanding redirect data while waiting for FollowRedirect
381 // to be called. Values default to their most restrictive in case they are
382 // not set.
383 struct RedirectData {
384 bool is_directory = false;
385 base::FilePath profile_path;
386 network::ResourceRequest request;
387 network::mojom::URLLoaderRequest loader;
388 DirectoryLoadingPolicy directory_loading_policy =
389 DirectoryLoadingPolicy::kFail;
390 FileAccessPolicy file_access_policy = FileAccessPolicy::kRestricted;
391 LinkFollowingPolicy link_following_policy =
392 LinkFollowingPolicy::kDoNotFollow;
393 std::unique_ptr<FileURLLoaderObserver> observer;
394 scoped_refptr<net::HttpResponseHeaders> extra_response_headers;
395 };
396
Ken Rockot314714c2017-11-05 23:36:24397 FileURLLoader() : binding_(this) {}
398 ~FileURLLoader() override = default;
399
400 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45401 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18402 network::mojom::URLLoaderRequest loader,
403 network::mojom::URLLoaderClientPtrInfo client_info,
Ken Rockot6414c4d92017-11-08 19:58:32404 DirectoryLoadingPolicy directory_loading_policy,
405 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42406 LinkFollowingPolicy link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25407 std::unique_ptr<FileURLLoaderObserver> observer,
408 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
John Abd-El-Malek46248032018-01-17 19:11:23409 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24410 head.request_start = base::TimeTicks::Now();
411 head.response_start = base::TimeTicks::Now();
Ken Rockota0dfaca12018-02-15 07:26:25412 head.headers = extra_response_headers;
Ken Rockot314714c2017-11-05 23:36:24413 binding_.Bind(std::move(loader));
414 binding_.set_connection_error_handler(base::BindOnce(
415 &FileURLLoader::OnConnectionError, base::Unretained(this)));
416
Chris Mumford90b1a0d2018-09-26 20:10:43417 client_.Bind(std::move(client_info));
Ken Rockot314714c2017-11-05 23:36:24418
419 base::FilePath path;
420 if (!net::FileURLToFilePath(request.url, &path)) {
Chris Mumford90b1a0d2018-09-26 20:10:43421 OnClientComplete(net::ERR_FAILED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24422 return;
423 }
424
425 base::File::Info info;
426 if (!base::GetFileInfo(path, &info)) {
Chris Mumford90b1a0d2018-09-26 20:10:43427 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24428 return;
429 }
430
Ken Rockot6414c4d92017-11-08 19:58:32431 if (info.is_directory) {
432 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Chris Mumford90b1a0d2018-09-26 20:10:43433 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
Ken Rockot6414c4d92017-11-08 19:58:32434 return;
435 }
Ken Rockot314714c2017-11-05 23:36:24436
Ken Rockot6414c4d92017-11-08 19:58:32437 DCHECK_EQ(directory_loading_policy,
438 DirectoryLoadingPolicy::kRespondWithListing);
439
Chris Mumford90b1a0d2018-09-26 20:10:43440 net::RedirectInfo redirect_info;
441 redirect_info.new_method = "GET";
442 redirect_info.status_code = 301;
443 redirect_info.new_url = path.EndsWithSeparator()
444 ? request.url
445 : AppendUrlSeparator(request.url);
446 head.encoded_data_length = 0;
Ken Rockot6414c4d92017-11-08 19:58:32447
Chris Mumford90b1a0d2018-09-26 20:10:43448 redirect_data_ = std::make_unique<RedirectData>();
449 redirect_data_->is_directory = true;
450 redirect_data_->profile_path = std::move(profile_path);
451 redirect_data_->request = request;
452 redirect_data_->directory_loading_policy = directory_loading_policy;
453 redirect_data_->file_access_policy = file_access_policy;
454 redirect_data_->link_following_policy = link_following_policy;
455 redirect_data_->request.url = redirect_info.new_url;
456 redirect_data_->observer = std::move(observer);
457 redirect_data_->extra_response_headers =
458 std::move(extra_response_headers);
Ken Rockot314714c2017-11-05 23:36:24459
Chris Mumford90b1a0d2018-09-26 20:10:43460 client_->OnReceiveRedirect(redirect_info, head);
Ken Rockot314714c2017-11-05 23:36:24461 return;
462 }
463
464#if defined(OS_WIN)
465 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32466 if (link_following_policy == LinkFollowingPolicy::kFollow &&
467 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24468 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
469 // Follow Windows shortcuts
Chris Mumford90b1a0d2018-09-26 20:10:43470 redirect_data_ = std::make_unique<RedirectData>();
471 if (!base::GetFileInfo(shortcut_target, &info)) {
472 OnClientComplete(net::ERR_FILE_NOT_FOUND, std::move(observer));
473 return;
474 }
475
Ken Rockot314714c2017-11-05 23:36:24476 GURL new_url = net::FilePathToFileURL(shortcut_target);
Chris Mumford90b1a0d2018-09-26 20:10:43477 if (info.is_directory && !path.EndsWithSeparator())
478 new_url = AppendUrlSeparator(new_url);
Ken Rockot314714c2017-11-05 23:36:24479
480 net::RedirectInfo redirect_info;
481 redirect_info.new_method = "GET";
482 redirect_info.status_code = 301;
483 redirect_info.new_url = new_url;
484 head.encoded_data_length = 0;
Ken Rockot314714c2017-11-05 23:36:24485
Chris Mumford90b1a0d2018-09-26 20:10:43486 redirect_data_->is_directory = info.is_directory;
487 redirect_data_->profile_path = std::move(profile_path);
488 redirect_data_->request = request;
489 redirect_data_->directory_loading_policy = directory_loading_policy;
490 redirect_data_->file_access_policy = file_access_policy;
491 redirect_data_->link_following_policy = link_following_policy;
492 redirect_data_->request.url = redirect_info.new_url;
493 redirect_data_->observer = std::move(observer);
494 redirect_data_->extra_response_headers =
495 std::move(extra_response_headers);
496
497 client_->OnReceiveRedirect(redirect_info, head);
498 return;
Ken Rockot314714c2017-11-05 23:36:24499 }
500#endif // defined(OS_WIN)
501
Ken Rockot6414c4d92017-11-08 19:58:32502 if (file_access_policy == FileAccessPolicy::kRestricted &&
503 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24504 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Chris Mumford90b1a0d2018-09-26 20:10:43505 OnClientComplete(net::ERR_ACCESS_DENIED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24506 return;
507 }
508
509 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
510 if (!pipe.consumer_handle.is_valid()) {
Chris Mumford90b1a0d2018-09-26 20:10:43511 OnClientComplete(net::ERR_FAILED, std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24512 return;
513 }
514
515 // Should never be possible for this to be a directory. If FileURLLoader
516 // is used to respond to a directory request, it must be because the URL
517 // path didn't have a trailing path separator. In that case we finish with
518 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
519 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42520 if (observer)
521 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24522
523 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumford8f812662018-02-22 00:27:57524 if (!file.IsValid()) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10525 if (observer) {
526 observer->OnBytesRead(nullptr, 0u, file.error_details());
Chris Mumford8f812662018-02-22 00:27:57527 observer->OnDoneReading();
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10528 }
529 net::Error net_error = net::FileErrorToNetError(file.error_details());
Chris Mumford90b1a0d2018-09-26 20:10:43530 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
531 client_.reset();
Chris Mumford8f812662018-02-22 00:27:57532 MaybeDeleteSelf();
533 return;
534 }
Ken Rockot314714c2017-11-05 23:36:24535 char initial_read_buffer[net::kMaxBytesToSniff];
536 int initial_read_result =
537 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
538 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42539 base::File::Error read_error = base::File::GetLastFileError();
540 DCHECK_NE(base::File::FILE_OK, read_error);
541 if (observer) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10542 // This can happen when the file is unreadable (which can happen during
543 // corruption). We need to be sure to inform
544 // the observer that we've finished reading so that it can proceed.
Chris Mumfordb5df8ef22017-12-20 17:47:42545 observer->OnBytesRead(nullptr, 0u, read_error);
546 observer->OnDoneReading();
547 }
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10548 net::Error net_error = net::FileErrorToNetError(read_error);
Chris Mumford90b1a0d2018-09-26 20:10:43549 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
550 client_.reset();
551 MaybeDeleteSelf();
Ken Rockot314714c2017-11-05 23:36:24552 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42553 } else if (observer) {
554 observer->OnBytesRead(initial_read_buffer, initial_read_result,
555 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24556 }
557 size_t initial_read_size = static_cast<size_t>(initial_read_result);
558
559 std::string range_header;
560 net::HttpByteRange byte_range;
561 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
562 &range_header)) {
563 // Handle a simple Range header for a single range.
564 std::vector<net::HttpByteRange> ranges;
565 bool fail = false;
566 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
567 ranges.size() == 1) {
568 byte_range = ranges[0];
569 if (!byte_range.ComputeBounds(info.size))
570 fail = true;
571 } else {
572 fail = true;
573 }
574
575 if (fail) {
Chris Mumford90b1a0d2018-09-26 20:10:43576 OnClientComplete(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
577 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24578 return;
579 }
580 }
581
582 size_t first_byte_to_send = 0;
583 size_t total_bytes_to_send = static_cast<size_t>(info.size);
Antonio Gomes402dbf52018-08-02 11:06:41584
Ken Rockot314714c2017-11-05 23:36:24585 if (byte_range.IsValid()) {
586 first_byte_to_send =
587 static_cast<size_t>(byte_range.first_byte_position());
588 total_bytes_to_send =
589 static_cast<size_t>(byte_range.last_byte_position()) -
590 first_byte_to_send + 1;
591 }
592
Eric Roman7f6604c2018-10-02 22:50:02593 total_bytes_written_ = static_cast<size_t>(total_bytes_to_send);
594
Ken Rockot314714c2017-11-05 23:36:24595 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
596
597 if (first_byte_to_send < initial_read_size) {
598 // Write any data we read for MIME sniffing, constraining by range where
599 // applicable. This will always fit in the pipe (see assertion near
600 // |kDefaultFileUrlPipeSize| definition).
601 uint32_t write_size = std::min(
602 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
603 static_cast<uint32_t>(total_bytes_to_send));
604 const uint32_t expected_write_size = write_size;
605 MojoResult result = pipe.producer_handle->WriteData(
606 &initial_read_buffer[first_byte_to_send], &write_size,
607 MOJO_WRITE_DATA_FLAG_NONE);
608 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42609 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24610 return;
611 }
612
613 // Discount the bytes we just sent from the total range.
614 first_byte_to_send = initial_read_size;
615 total_bytes_to_send -= write_size;
616 }
617
618 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
Matt Menkefcbb1bd72018-01-31 21:53:12619 net::SniffMimeType(
620 initial_read_buffer, initial_read_result, request.url, head.mime_type,
621 GetContentClient()->browser()->ForceSniffingFileUrlsForHtml()
622 ? net::ForceSniffFileUrlsForHtml::kEnabled
623 : net::ForceSniffFileUrlsForHtml::kDisabled,
624 &head.mime_type);
Makoto Shimazu21950e82018-08-02 12:37:14625 head.did_mime_sniff = true;
Ken Rockot314714c2017-11-05 23:36:24626 }
Chris Mumford8f812662018-02-22 00:27:57627 if (head.headers) {
628 head.headers->AddHeader(
629 base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType,
630 head.mime_type.c_str()));
631 }
Chris Mumford90b1a0d2018-09-26 20:10:43632 client_->OnReceiveResponse(head);
633 client_->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
Ken Rockot314714c2017-11-05 23:36:24634
635 if (total_bytes_to_send == 0) {
636 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42637 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24638 return;
639 }
640
641 // In case of a range request, seek to the appropriate position before
642 // sending the remaining bytes asynchronously. Under normal conditions
643 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42644 int new_position = file.Seek(base::File::FROM_BEGIN,
645 static_cast<int64_t>(first_byte_to_send));
646 if (observer)
647 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24648
649 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42650 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24651 data_producer_->WriteFromFile(
652 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42653 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
654 nullptr));
Ken Rockot314714c2017-11-05 23:36:24655 }
656
657 void OnConnectionError() {
658 binding_.Close();
659 MaybeDeleteSelf();
660 }
661
Chris Mumford90b1a0d2018-09-26 20:10:43662 void OnClientComplete(net::Error net_error,
663 std::unique_ptr<FileURLLoaderObserver> observer) {
664 client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
665 client_.reset();
666 if (observer)
667 observer->OnDoneReading();
668 MaybeDeleteSelf();
669 }
670
Ken Rockot314714c2017-11-05 23:36:24671 void MaybeDeleteSelf() {
672 if (!binding_.is_bound() && !client_.is_bound())
673 delete this;
674 }
675
Chris Mumfordb5df8ef22017-12-20 17:47:42676 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
677 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34678 // All the data has been written now. Close the data pipe. The consumer will
679 // be notified that there will be no more data to read from now.
680 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42681 if (observer)
682 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34683
Antonio Gomes402dbf52018-08-02 11:06:41684 if (result == MOJO_RESULT_OK) {
685 network::URLLoaderCompletionStatus status(net::OK);
686 status.encoded_data_length = total_bytes_written_;
687 status.encoded_body_length = total_bytes_written_;
688 status.decoded_body_length = total_bytes_written_;
689 client_->OnComplete(status);
690 } else {
Takashi Toyoshimaaa278662017-11-20 11:11:26691 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Antonio Gomes402dbf52018-08-02 11:06:41692 }
Ken Rockot314714c2017-11-05 23:36:24693 client_.reset();
694 MaybeDeleteSelf();
695 }
696
697 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
John Abd-El-Malekb165dc52018-01-18 17:12:18698 mojo::Binding<network::mojom::URLLoader> binding_;
699 network::mojom::URLLoaderClientPtr client_;
Chris Mumford90b1a0d2018-09-26 20:10:43700 std::unique_ptr<RedirectData> redirect_data_;
Ken Rockot314714c2017-11-05 23:36:24701
Eric Roman7f6604c2018-10-02 22:50:02702 // In case of successful loads, this holds the total number of bytes written
703 // to the response (this may be smaller than the total size of the file when
704 // a byte range was requested).
Antonio Gomes402dbf52018-08-02 11:06:41705 // It is used to set some of the URLLoaderCompletionStatus data passed back
706 // to the URLLoaderClients (eg SimpleURLLoader).
707 size_t total_bytes_written_ = 0;
708
Ken Rockot314714c2017-11-05 23:36:24709 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
710};
711
712} // namespace
713
714FileURLLoaderFactory::FileURLLoaderFactory(
715 const base::FilePath& profile_path,
716 scoped_refptr<base::SequencedTaskRunner> task_runner)
717 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
718
719FileURLLoaderFactory::~FileURLLoaderFactory() = default;
720
Ken Rockot314714c2017-11-05 23:36:24721void FileURLLoaderFactory::CreateLoaderAndStart(
John Abd-El-Malekb165dc52018-01-18 17:12:18722 network::mojom::URLLoaderRequest loader,
Ken Rockot314714c2017-11-05 23:36:24723 int32_t routing_id,
724 int32_t request_id,
725 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45726 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18727 network::mojom::URLLoaderClientPtr client,
Ken Rockot314714c2017-11-05 23:36:24728 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
729 base::FilePath file_path;
Chris Mumford90b1a0d2018-09-26 20:10:43730 if (!net::FileURLToFilePath(request.url, &file_path)) {
731 client->OnComplete(
732 network::URLLoaderCompletionStatus(net::ERR_INVALID_URL));
733 return;
734 }
Yutaka Hirano6eddf7382018-10-10 06:44:04735
736 // FileURLLoader doesn't support CORS and it's not covered by CORSURLLoader,
737 // so we need to reject requests that need CORS manually.
738 if (ShouldFailRequestDueToCORS(request)) {
739 client->OnComplete(
740 network::URLLoaderCompletionStatus(network::CORSErrorStatus(
741 network::mojom::CORSError::kCORSDisabledScheme)));
742 return;
743 }
744
Chris Mumford90b1a0d2018-09-26 20:10:43745 if (file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
Ken Rockot314714c2017-11-05 23:36:24746 task_runner_->PostTask(
747 FROM_HERE,
748 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42749 request, std::move(loader), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57750 std::unique_ptr<FileURLLoaderObserver>(), nullptr));
Ken Rockot314714c2017-11-05 23:36:24751 } else {
752 task_runner_->PostTask(
753 FROM_HERE,
754 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32755 std::move(loader), client.PassInterface(),
756 DirectoryLoadingPolicy::kRespondWithListing,
757 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42758 LinkFollowingPolicy::kFollow,
Ken Rockota0dfaca12018-02-15 07:26:25759 std::unique_ptr<FileURLLoaderObserver>(),
760 nullptr /* extra_response_headers */));
Ken Rockot314714c2017-11-05 23:36:24761 }
762}
763
John Abd-El-Malekb165dc52018-01-18 17:12:18764void FileURLLoaderFactory::Clone(
765 network::mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32766 bindings_.AddBinding(this, std::move(loader));
767}
768
Ken Rockota0dfaca12018-02-15 07:26:25769void CreateFileURLLoader(
770 const network::ResourceRequest& request,
771 network::mojom::URLLoaderRequest loader,
772 network::mojom::URLLoaderClientPtr client,
773 std::unique_ptr<FileURLLoaderObserver> observer,
774 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot6414c4d92017-11-08 19:58:32775 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
Gabriel Charetteb10aeeb2018-07-26 20:15:00776 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
Ken Rockot6414c4d92017-11-08 19:58:32777 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
778 task_runner->PostTask(
779 FROM_HERE,
780 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
781 std::move(loader), client.PassInterface(),
782 DirectoryLoadingPolicy::kFail,
783 FileAccessPolicy::kUnrestricted,
Ken Rockota0dfaca12018-02-15 07:26:25784 LinkFollowingPolicy::kDoNotFollow, std::move(observer),
785 std::move(extra_response_headers)));
Ken Rockot314714c2017-11-05 23:36:24786}
787
Antonio Gomese1a02192018-08-01 15:39:44788std::unique_ptr<network::mojom::URLLoaderFactory> CreateFileURLLoaderFactory(
789 const base::FilePath& profile_path) {
790 return std::make_unique<content::FileURLLoaderFactory>(
791 profile_path, base::CreateSequencedTaskRunnerWithTraits(
792 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
793 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
794}
795
Ken Rockot314714c2017-11-05 23:36:24796} // namespace content