blob: 1918352d54c6a2dec26ebc798d6bde67dd2809e1 [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:
Ken Rockot314714c2017-11-05 23:36:24110 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26111 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24112 void SetPriority(net::RequestPriority priority,
113 int32_t intra_priority_value) override {}
114 void PauseReadingBodyFromNet() override {}
115 void ResumeReadingBodyFromNet() override {}
116
117 private:
118 FileURLDirectoryLoader() : binding_(this) {}
119 ~FileURLDirectoryLoader() override = default;
120
121 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45122 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18123 network::mojom::URLLoaderRequest loader,
124 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumford8f812662018-02-22 00:27:57125 std::unique_ptr<content::FileURLLoaderObserver> observer,
126 scoped_refptr<net::HttpResponseHeaders> response_headers) {
Ken Rockot314714c2017-11-05 23:36:24127 binding_.Bind(std::move(loader));
128 binding_.set_connection_error_handler(base::BindOnce(
129 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
130
John Abd-El-Malekb165dc52018-01-18 17:12:18131 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24132 client.Bind(std::move(client_info));
133
134 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26135 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24136 return;
137 }
138
139 base::File::Info info;
140 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26141 client->OnComplete(
142 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24143 return;
144 }
145
146 if (!GetContentClient()->browser()->IsFileAccessAllowed(
147 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26148 client->OnComplete(
149 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24150 return;
151 }
152
153 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
154 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26155 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24156 return;
157 }
158
John Abd-El-Malek46248032018-01-17 19:11:23159 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24160 head.mime_type = "text/html";
161 head.charset = "utf-8";
Andrey Kosyakov87cd9252018-03-27 16:58:27162 client->OnReceiveResponse(head, nullptr);
Ken Rockot314714c2017-11-05 23:36:24163 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
164 client_ = std::move(client);
165
166 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
167 lister_->Start();
168
169 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
170 std::move(pipe.producer_handle));
171 }
172
173 void OnConnectionError() {
174 binding_.Close();
175 MaybeDeleteSelf();
176 }
177
178 void MaybeDeleteSelf() {
179 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
180 delete this;
181 }
182
183 // net::DirectoryLister::DirectoryListerDelegate:
184 void OnListFile(
185 const net::DirectoryLister::DirectoryListerData& data) override {
186 if (!wrote_header_) {
187 wrote_header_ = true;
188
189#if defined(OS_WIN)
190 const base::string16& title = path_.value();
191#elif defined(OS_POSIX)
192 const base::string16& title =
193 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
194#endif
195 pending_data_.append(net::GetDirectoryListingHeader(title));
196
197 // If not a top-level directory, add a link to the parent directory. To
198 // figure this out, first normalize |path_| by stripping trailing
199 // separators. Then compare the result to its DirName(). For the top-level
200 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
201 // DirName().
202 base::FilePath stripped_path = path_.StripTrailingSeparators();
203 if (stripped_path != stripped_path.DirName())
204 pending_data_.append(net::GetParentDirectoryLink());
205 }
206
207 // Skip current / parent links from the listing.
208 base::FilePath filename = data.info.GetName();
209 if (filename.value() != base::FilePath::kCurrentDirectory &&
210 filename.value() != base::FilePath::kParentDirectory) {
211#if defined(OS_WIN)
212 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
213#elif defined(OS_POSIX)
214 const std::string& raw_bytes = filename.value();
215#endif
216 pending_data_.append(net::GetDirectoryListingEntry(
217 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
218 data.info.GetSize(), data.info.GetLastModifiedTime()));
219 }
220
221 MaybeTransferPendingData();
222 }
223
224 void OnListDone(int error) override {
225 listing_result_ = error;
226 lister_.reset();
227 MaybeDeleteSelf();
228 }
229
230 void MaybeTransferPendingData() {
231 if (transfer_in_progress_)
232 return;
233
234 transfer_in_progress_ = true;
235 data_producer_->Write(pending_data_,
Daniel Murphy0ecba612018-02-07 20:56:18236 mojo::StringDataPipeProducer::AsyncWritingMode::
237 STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
Ken Rockot314714c2017-11-05 23:36:24238 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
239 base::Unretained(this)));
240 // The producer above will have already copied any parts of |pending_data_|
241 // that couldn't be written immediately, so we can wipe it out here to begin
242 // accumulating more data.
243 pending_data_.clear();
244 }
245
246 void OnDataWritten(MojoResult result) {
247 transfer_in_progress_ = false;
248
249 int completion_status;
250 if (result == MOJO_RESULT_OK) {
251 if (!pending_data_.empty()) {
252 // Keep flushing the data buffer as long as it's non-empty and pipe
253 // writes are succeeding.
254 MaybeTransferPendingData();
255 return;
256 }
257
258 // If there's no pending data but the lister is still active, we simply
259 // wait for more listing results.
260 if (lister_)
261 return;
262
263 // At this point we know the listing is complete and all available data
264 // has been transferred. We inherit the status of the listing operation.
265 completion_status = listing_result_;
266 } else {
267 completion_status = net::ERR_FAILED;
268 }
269
arthursonzogni3a4ca9f2017-12-07 17:58:34270 // All the data has been written now. Close the data pipe. The consumer will
271 // be notified that there will be no more data to read from now.
272 data_producer_.reset();
273
Takashi Toyoshimaaa278662017-11-20 11:11:26274 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24275 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34276
Ken Rockot314714c2017-11-05 23:36:24277 MaybeDeleteSelf();
278 }
279
280 base::FilePath path_;
281 std::unique_ptr<net::DirectoryLister> lister_;
282 bool wrote_header_ = false;
283 int listing_result_;
284
John Abd-El-Malekb165dc52018-01-18 17:12:18285 mojo::Binding<network::mojom::URLLoader> binding_;
286 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24287
288 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
289 std::string pending_data_;
290 bool transfer_in_progress_ = false;
291
292 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
293};
294
John Abd-El-Malekb165dc52018-01-18 17:12:18295class FileURLLoader : public network::mojom::URLLoader {
Ken Rockot314714c2017-11-05 23:36:24296 public:
Ken Rockota0dfaca12018-02-15 07:26:25297 static void CreateAndStart(
298 const base::FilePath& profile_path,
299 const network::ResourceRequest& request,
300 network::mojom::URLLoaderRequest loader,
301 network::mojom::URLLoaderClientPtrInfo client_info,
302 DirectoryLoadingPolicy directory_loading_policy,
303 FileAccessPolicy file_access_policy,
304 LinkFollowingPolicy link_following_policy,
305 std::unique_ptr<FileURLLoaderObserver> observer,
306 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot314714c2017-11-05 23:36:24307 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
308 // bindings are alive - essentially until either the client gives up or all
309 // file data has been sent to it.
310 auto* file_url_loader = new FileURLLoader;
Ken Rockota0dfaca12018-02-15 07:26:25311 file_url_loader->Start(
312 profile_path, request, std::move(loader), std::move(client_info),
313 directory_loading_policy, file_access_policy, link_following_policy,
314 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24315 }
316
John Abd-El-Malekb165dc52018-01-18 17:12:18317 // network::mojom::URLLoader:
Ken Rockot314714c2017-11-05 23:36:24318 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26319 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24320 void SetPriority(net::RequestPriority priority,
321 int32_t intra_priority_value) override {}
322 void PauseReadingBodyFromNet() override {}
323 void ResumeReadingBodyFromNet() override {}
324
325 private:
326 FileURLLoader() : binding_(this) {}
327 ~FileURLLoader() override = default;
328
329 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45330 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18331 network::mojom::URLLoaderRequest loader,
332 network::mojom::URLLoaderClientPtrInfo client_info,
Ken Rockot6414c4d92017-11-08 19:58:32333 DirectoryLoadingPolicy directory_loading_policy,
334 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42335 LinkFollowingPolicy link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25336 std::unique_ptr<FileURLLoaderObserver> observer,
337 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
John Abd-El-Malek46248032018-01-17 19:11:23338 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24339 head.request_start = base::TimeTicks::Now();
340 head.response_start = base::TimeTicks::Now();
Ken Rockota0dfaca12018-02-15 07:26:25341 head.headers = extra_response_headers;
Ken Rockot314714c2017-11-05 23:36:24342 binding_.Bind(std::move(loader));
343 binding_.set_connection_error_handler(base::BindOnce(
344 &FileURLLoader::OnConnectionError, base::Unretained(this)));
345
John Abd-El-Malekb165dc52018-01-18 17:12:18346 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24347 client.Bind(std::move(client_info));
348
349 base::FilePath path;
350 if (!net::FileURLToFilePath(request.url, &path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26351 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42352 if (observer)
353 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24354 return;
355 }
356
357 base::File::Info info;
358 if (!base::GetFileInfo(path, &info)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26359 client->OnComplete(
360 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42361 if (observer)
362 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24363 return;
364 }
365
Ken Rockot6414c4d92017-11-08 19:58:32366 if (info.is_directory) {
367 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26368 client->OnComplete(
369 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42370 if (observer)
371 observer->OnDoneReading();
Ken Rockot6414c4d92017-11-08 19:58:32372 return;
373 }
Ken Rockot314714c2017-11-05 23:36:24374
Ken Rockot6414c4d92017-11-08 19:58:32375 DCHECK_EQ(directory_loading_policy,
376 DirectoryLoadingPolicy::kRespondWithListing);
377
378 GURL directory_url = request.url;
379 if (!path.EndsWithSeparator()) {
380 // If the named path is a directory with no trailing slash, redirect to
381 // the same path, but with a trailing slash.
382 std::string new_path = directory_url.path() + '/';
383 GURL::Replacements replacements;
384 replacements.SetPathStr(new_path);
385 directory_url = directory_url.ReplaceComponents(replacements);
386
387 net::RedirectInfo redirect_info;
388 redirect_info.new_method = "GET";
389 redirect_info.status_code = 301;
390 redirect_info.new_url = directory_url;
391 head.encoded_data_length = 0;
392 client->OnReceiveRedirect(redirect_info, head);
393 }
Ken Rockot314714c2017-11-05 23:36:24394
395 // Restart the request with a directory loader.
John Abd-El-Malek1df61792018-01-12 20:40:45396 network::ResourceRequest new_request = request;
Ken Rockot6414c4d92017-11-08 19:58:32397 new_request.url = directory_url;
Ken Rockot314714c2017-11-05 23:36:24398 FileURLDirectoryLoader::CreateAndStart(
Chris Mumfordb5df8ef22017-12-20 17:47:42399 profile_path, new_request, binding_.Unbind(), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57400 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24401 MaybeDeleteSelf();
402 return;
403 }
404
405#if defined(OS_WIN)
406 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32407 if (link_following_policy == LinkFollowingPolicy::kFollow &&
408 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24409 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
410 // Follow Windows shortcuts
411 GURL new_url = net::FilePathToFileURL(shortcut_target);
412
413 net::RedirectInfo redirect_info;
414 redirect_info.new_method = "GET";
415 redirect_info.status_code = 301;
416 redirect_info.new_url = new_url;
417 head.encoded_data_length = 0;
418 client->OnReceiveRedirect(redirect_info, head);
419
420 // Restart the request with the new URL.
John Abd-El-Malek1df61792018-01-12 20:40:45421 network::ResourceRequest new_request = request;
Ken Rockot314714c2017-11-05 23:36:24422 new_request.url = redirect_info.new_url;
423 return Start(profile_path, request, binding_.Unbind(),
Ken Rockot6414c4d92017-11-08 19:58:32424 client.PassInterface(), directory_loading_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42425 file_access_policy, link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25426 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24427 }
428#endif // defined(OS_WIN)
429
Ken Rockot6414c4d92017-11-08 19:58:32430 if (file_access_policy == FileAccessPolicy::kRestricted &&
431 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24432 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26433 client->OnComplete(
434 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Chris Mumfordb5df8ef22017-12-20 17:47:42435 if (observer)
436 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24437 return;
438 }
439
440 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
441 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26442 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42443 if (observer)
444 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24445 return;
446 }
447
448 // Should never be possible for this to be a directory. If FileURLLoader
449 // is used to respond to a directory request, it must be because the URL
450 // path didn't have a trailing path separator. In that case we finish with
451 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
452 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42453 if (observer)
454 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24455
456 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumford8f812662018-02-22 00:27:57457 if (!file.IsValid()) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10458 if (observer) {
459 observer->OnBytesRead(nullptr, 0u, file.error_details());
Chris Mumford8f812662018-02-22 00:27:57460 observer->OnDoneReading();
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10461 }
462 net::Error net_error = net::FileErrorToNetError(file.error_details());
Chris Mumford8f812662018-02-22 00:27:57463 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
464 MaybeDeleteSelf();
465 return;
466 }
Ken Rockot314714c2017-11-05 23:36:24467 char initial_read_buffer[net::kMaxBytesToSniff];
468 int initial_read_result =
469 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
470 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42471 base::File::Error read_error = base::File::GetLastFileError();
472 DCHECK_NE(base::File::FILE_OK, read_error);
473 if (observer) {
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10474 // This can happen when the file is unreadable (which can happen during
475 // corruption). We need to be sure to inform
476 // the observer that we've finished reading so that it can proceed.
Chris Mumfordb5df8ef22017-12-20 17:47:42477 observer->OnBytesRead(nullptr, 0u, read_error);
478 observer->OnDoneReading();
479 }
Mikhail Atuchin9af3ff8c2018-04-19 21:15:10480 net::Error net_error = net::FileErrorToNetError(read_error);
Chris Mumfordb5df8ef22017-12-20 17:47:42481 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
Ken Rockot314714c2017-11-05 23:36:24482 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42483 } else if (observer) {
484 observer->OnBytesRead(initial_read_buffer, initial_read_result,
485 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24486 }
487 size_t initial_read_size = static_cast<size_t>(initial_read_result);
488
489 std::string range_header;
490 net::HttpByteRange byte_range;
491 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
492 &range_header)) {
493 // Handle a simple Range header for a single range.
494 std::vector<net::HttpByteRange> ranges;
495 bool fail = false;
496 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
497 ranges.size() == 1) {
498 byte_range = ranges[0];
499 if (!byte_range.ComputeBounds(info.size))
500 fail = true;
501 } else {
502 fail = true;
503 }
504
505 if (fail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26506 client->OnComplete(network::URLLoaderCompletionStatus(
507 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
Chris Mumfordb5df8ef22017-12-20 17:47:42508 if (observer)
509 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24510 return;
511 }
512 }
513
514 size_t first_byte_to_send = 0;
515 size_t total_bytes_to_send = static_cast<size_t>(info.size);
516 if (byte_range.IsValid()) {
517 first_byte_to_send =
518 static_cast<size_t>(byte_range.first_byte_position());
519 total_bytes_to_send =
520 static_cast<size_t>(byte_range.last_byte_position()) -
521 first_byte_to_send + 1;
522 }
523
524 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
525
526 if (first_byte_to_send < initial_read_size) {
527 // Write any data we read for MIME sniffing, constraining by range where
528 // applicable. This will always fit in the pipe (see assertion near
529 // |kDefaultFileUrlPipeSize| definition).
530 uint32_t write_size = std::min(
531 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
532 static_cast<uint32_t>(total_bytes_to_send));
533 const uint32_t expected_write_size = write_size;
534 MojoResult result = pipe.producer_handle->WriteData(
535 &initial_read_buffer[first_byte_to_send], &write_size,
536 MOJO_WRITE_DATA_FLAG_NONE);
537 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42538 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24539 return;
540 }
541
542 // Discount the bytes we just sent from the total range.
543 first_byte_to_send = initial_read_size;
544 total_bytes_to_send -= write_size;
545 }
546
547 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
Matt Menkefcbb1bd72018-01-31 21:53:12548 net::SniffMimeType(
549 initial_read_buffer, initial_read_result, request.url, head.mime_type,
550 GetContentClient()->browser()->ForceSniffingFileUrlsForHtml()
551 ? net::ForceSniffFileUrlsForHtml::kEnabled
552 : net::ForceSniffFileUrlsForHtml::kDisabled,
553 &head.mime_type);
Ken Rockot314714c2017-11-05 23:36:24554 }
Chris Mumford8f812662018-02-22 00:27:57555 if (head.headers) {
556 head.headers->AddHeader(
557 base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType,
558 head.mime_type.c_str()));
559 }
Andrey Kosyakov87cd9252018-03-27 16:58:27560 client->OnReceiveResponse(head, nullptr);
Ken Rockot314714c2017-11-05 23:36:24561 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
562 client_ = std::move(client);
563
564 if (total_bytes_to_send == 0) {
565 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42566 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24567 return;
568 }
569
570 // In case of a range request, seek to the appropriate position before
571 // sending the remaining bytes asynchronously. Under normal conditions
572 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42573 int new_position = file.Seek(base::File::FROM_BEGIN,
574 static_cast<int64_t>(first_byte_to_send));
575 if (observer)
576 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24577
578 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42579 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24580 data_producer_->WriteFromFile(
581 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42582 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
583 nullptr));
Ken Rockot314714c2017-11-05 23:36:24584 }
585
586 void OnConnectionError() {
587 binding_.Close();
588 MaybeDeleteSelf();
589 }
590
591 void MaybeDeleteSelf() {
592 if (!binding_.is_bound() && !client_.is_bound())
593 delete this;
594 }
595
Chris Mumfordb5df8ef22017-12-20 17:47:42596 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
597 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34598 // All the data has been written now. Close the data pipe. The consumer will
599 // be notified that there will be no more data to read from now.
600 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42601 if (observer)
602 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34603
Ken Rockot314714c2017-11-05 23:36:24604 if (result == MOJO_RESULT_OK)
Takashi Toyoshimaaa278662017-11-20 11:11:26605 client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
Ken Rockot314714c2017-11-05 23:36:24606 else
Takashi Toyoshimaaa278662017-11-20 11:11:26607 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24608 client_.reset();
609 MaybeDeleteSelf();
610 }
611
612 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
John Abd-El-Malekb165dc52018-01-18 17:12:18613 mojo::Binding<network::mojom::URLLoader> binding_;
614 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24615
616 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
617};
618
619} // namespace
620
621FileURLLoaderFactory::FileURLLoaderFactory(
622 const base::FilePath& profile_path,
623 scoped_refptr<base::SequencedTaskRunner> task_runner)
624 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
625
626FileURLLoaderFactory::~FileURLLoaderFactory() = default;
627
Ken Rockot314714c2017-11-05 23:36:24628void FileURLLoaderFactory::CreateLoaderAndStart(
John Abd-El-Malekb165dc52018-01-18 17:12:18629 network::mojom::URLLoaderRequest loader,
Ken Rockot314714c2017-11-05 23:36:24630 int32_t routing_id,
631 int32_t request_id,
632 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45633 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18634 network::mojom::URLLoaderClientPtr client,
Ken Rockot314714c2017-11-05 23:36:24635 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
Marijn Kruisselbrink351b4762018-03-13 18:44:32636 DCHECK(!request.download_to_file);
Ken Rockot314714c2017-11-05 23:36:24637 base::FilePath file_path;
638 const bool is_file = net::FileURLToFilePath(request.url, &file_path);
639 if (is_file && file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
640 task_runner_->PostTask(
641 FROM_HERE,
642 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42643 request, std::move(loader), client.PassInterface(),
Chris Mumford8f812662018-02-22 00:27:57644 std::unique_ptr<FileURLLoaderObserver>(), nullptr));
Ken Rockot314714c2017-11-05 23:36:24645 } else {
646 task_runner_->PostTask(
647 FROM_HERE,
648 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32649 std::move(loader), client.PassInterface(),
650 DirectoryLoadingPolicy::kRespondWithListing,
651 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42652 LinkFollowingPolicy::kFollow,
Ken Rockota0dfaca12018-02-15 07:26:25653 std::unique_ptr<FileURLLoaderObserver>(),
654 nullptr /* extra_response_headers */));
Ken Rockot314714c2017-11-05 23:36:24655 }
656}
657
John Abd-El-Malekb165dc52018-01-18 17:12:18658void FileURLLoaderFactory::Clone(
659 network::mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32660 bindings_.AddBinding(this, std::move(loader));
661}
662
Ken Rockota0dfaca12018-02-15 07:26:25663void CreateFileURLLoader(
664 const network::ResourceRequest& request,
665 network::mojom::URLLoaderRequest loader,
666 network::mojom::URLLoaderClientPtr client,
667 std::unique_ptr<FileURLLoaderObserver> observer,
668 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot6414c4d92017-11-08 19:58:32669 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
670 {base::MayBlock(), base::TaskPriority::BACKGROUND,
671 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
672 task_runner->PostTask(
673 FROM_HERE,
674 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
675 std::move(loader), client.PassInterface(),
676 DirectoryLoadingPolicy::kFail,
677 FileAccessPolicy::kUnrestricted,
Ken Rockota0dfaca12018-02-15 07:26:25678 LinkFollowingPolicy::kDoNotFollow, std::move(observer),
679 std::move(extra_response_headers)));
Ken Rockot314714c2017-11-05 23:36:24680}
681
682} // namespace content