blob: b6882f3a589621f80115b72a87f4cd45cebc3b4c [file] [log] [blame]
Ken Rockot314714c2017-11-05 23:36:241// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/file_url_loader_factory.h"
6
7#include <memory>
8#include <string>
9#include <utility>
10
11#include "base/bind.h"
12#include "base/files/file.h"
13#include "base/files/file_path.h"
14#include "base/files/file_util.h"
15#include "base/macros.h"
16#include "base/numerics/safe_conversions.h"
17#include "base/strings/string16.h"
18#include "base/strings/string_util.h"
19#include "base/strings/sys_string_conversions.h"
20#include "base/strings/utf_string_conversions.h"
Ken Rockot6414c4d92017-11-08 19:58:3221#include "base/task_scheduler/post_task.h"
Ken Rockot314714c2017-11-05 23:36:2422#include "base/time/time.h"
23#include "build/build_config.h"
24#include "content/public/browser/content_browser_client.h"
Ken Rockot6414c4d92017-11-08 19:58:3225#include "content/public/browser/file_url_loader.h"
Matt Menkefcbb1bd72018-01-31 21:53:1226#include "content/public/common/content_client.h"
Ken Rockot314714c2017-11-05 23:36:2427#include "mojo/public/cpp/bindings/strong_binding.h"
28#include "mojo/public/cpp/system/data_pipe.h"
29#include "mojo/public/cpp/system/file_data_pipe_producer.h"
30#include "mojo/public/cpp/system/string_data_pipe_producer.h"
31#include "net/base/directory_lister.h"
32#include "net/base/directory_listing.h"
33#include "net/base/filename_util.h"
34#include "net/base/mime_sniffer.h"
35#include "net/base/mime_util.h"
36#include "net/base/net_errors.h"
37#include "net/http/http_byte_range.h"
38#include "net/http/http_util.h"
39#include "net/url_request/redirect_info.h"
John Abd-El-Malek1df61792018-01-12 20:40:4540#include "services/network/public/cpp/resource_request.h"
Ken Rockot54311e62018-02-10 19:01:5241#include "services/network/public/mojom/url_loader.mojom.h"
Chris Mumfordb5df8ef22017-12-20 17:47:4242#include "storage/common/fileapi/file_system_util.h"
Ken Rockot314714c2017-11-05 23:36:2443#include "url/gurl.h"
44
45#if defined(OS_WIN)
46#include "base/win/shortcut.h"
47#endif
48
49namespace content {
50namespace {
51
52constexpr size_t kDefaultFileUrlPipeSize = 65536;
53
54// Because this makes things simpler.
55static_assert(kDefaultFileUrlPipeSize >= net::kMaxBytesToSniff,
56 "Default file data pipe size must be at least as large as a MIME-"
57 "type sniffing buffer.");
58
Ken Rockot6414c4d92017-11-08 19:58:3259// Policy to control how a FileURLLoader will handle directory URLs.
60enum class DirectoryLoadingPolicy {
61 // File paths which refer to directories are allowed and will load as an
62 // HTML directory listing.
63 kRespondWithListing,
64
65 // File paths which refer to directories are treated as non-existent and
66 // will result in FILE_NOT_FOUND errors.
67 kFail,
68};
69
70// Policy to control whether or not file access constraints imposed by content
71// or its embedder should be honored by a FileURLLoader.
72enum class FileAccessPolicy {
73 // Enforces file acccess policy defined by content and/or its embedder.
74 kRestricted,
75
76 // Ignores file access policy, allowing contents to be loaded from any
77 // resolvable file path given.
78 kUnrestricted,
79};
80
81// Policy to control whether or not a FileURLLoader should follow filesystem
82// links (e.g. Windows shortcuts) where applicable.
83enum class LinkFollowingPolicy {
84 kFollow,
85 kDoNotFollow,
86};
87
Ken Rockot314714c2017-11-05 23:36:2488class FileURLDirectoryLoader
John Abd-El-Malekb165dc52018-01-18 17:12:1889 : public network::mojom::URLLoader,
Ken Rockot314714c2017-11-05 23:36:2490 public net::DirectoryLister::DirectoryListerDelegate {
91 public:
92 static void CreateAndStart(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:4593 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:1894 network::mojom::URLLoaderRequest loader,
95 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumfordb5df8ef22017-12-20 17:47:4296 std::unique_ptr<FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:2497 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
98 // bindings are alive - essentially until either the client gives up or all
99 // file data has been sent to it.
100 auto* file_url_loader = new FileURLDirectoryLoader;
101 file_url_loader->Start(profile_path, request, std::move(loader),
Chris Mumfordb5df8ef22017-12-20 17:47:42102 std::move(client_info), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24103 }
104
John Abd-El-Malekb165dc52018-01-18 17:12:18105 // network::mojom::URLLoader:
Ken Rockot314714c2017-11-05 23:36:24106 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26107 void ProceedWithResponse() override { NOTREACHED(); }
Ken Rockot314714c2017-11-05 23:36:24108 void SetPriority(net::RequestPriority priority,
109 int32_t intra_priority_value) override {}
110 void PauseReadingBodyFromNet() override {}
111 void ResumeReadingBodyFromNet() override {}
112
113 private:
114 FileURLDirectoryLoader() : binding_(this) {}
115 ~FileURLDirectoryLoader() override = default;
116
117 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45118 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18119 network::mojom::URLLoaderRequest loader,
120 network::mojom::URLLoaderClientPtrInfo client_info,
Chris Mumfordb5df8ef22017-12-20 17:47:42121 std::unique_ptr<content::FileURLLoaderObserver> observer) {
Ken Rockot314714c2017-11-05 23:36:24122 binding_.Bind(std::move(loader));
123 binding_.set_connection_error_handler(base::BindOnce(
124 &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
125
John Abd-El-Malekb165dc52018-01-18 17:12:18126 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24127 client.Bind(std::move(client_info));
128
129 if (!net::FileURLToFilePath(request.url, &path_)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26130 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24131 return;
132 }
133
134 base::File::Info info;
135 if (!base::GetFileInfo(path_, &info) || !info.is_directory) {
Takashi Toyoshimaaa278662017-11-20 11:11:26136 client->OnComplete(
137 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Ken Rockot314714c2017-11-05 23:36:24138 return;
139 }
140
141 if (!GetContentClient()->browser()->IsFileAccessAllowed(
142 path_, base::MakeAbsoluteFilePath(path_), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26143 client->OnComplete(
144 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Ken Rockot314714c2017-11-05 23:36:24145 return;
146 }
147
148 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
149 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26150 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24151 return;
152 }
153
John Abd-El-Malek46248032018-01-17 19:11:23154 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24155 head.mime_type = "text/html";
156 head.charset = "utf-8";
157 client->OnReceiveResponse(head, base::nullopt, nullptr);
158 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
159 client_ = std::move(client);
160
161 lister_ = std::make_unique<net::DirectoryLister>(path_, this);
162 lister_->Start();
163
164 data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
165 std::move(pipe.producer_handle));
166 }
167
168 void OnConnectionError() {
169 binding_.Close();
170 MaybeDeleteSelf();
171 }
172
173 void MaybeDeleteSelf() {
174 if (!binding_.is_bound() && !client_.is_bound() && !lister_)
175 delete this;
176 }
177
178 // net::DirectoryLister::DirectoryListerDelegate:
179 void OnListFile(
180 const net::DirectoryLister::DirectoryListerData& data) override {
181 if (!wrote_header_) {
182 wrote_header_ = true;
183
184#if defined(OS_WIN)
185 const base::string16& title = path_.value();
186#elif defined(OS_POSIX)
187 const base::string16& title =
188 base::WideToUTF16(base::SysNativeMBToWide(path_.value()));
189#endif
190 pending_data_.append(net::GetDirectoryListingHeader(title));
191
192 // If not a top-level directory, add a link to the parent directory. To
193 // figure this out, first normalize |path_| by stripping trailing
194 // separators. Then compare the result to its DirName(). For the top-level
195 // directory, e.g. "/" or "c:\\", the normalized path is equal to its own
196 // DirName().
197 base::FilePath stripped_path = path_.StripTrailingSeparators();
198 if (stripped_path != stripped_path.DirName())
199 pending_data_.append(net::GetParentDirectoryLink());
200 }
201
202 // Skip current / parent links from the listing.
203 base::FilePath filename = data.info.GetName();
204 if (filename.value() != base::FilePath::kCurrentDirectory &&
205 filename.value() != base::FilePath::kParentDirectory) {
206#if defined(OS_WIN)
207 std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
208#elif defined(OS_POSIX)
209 const std::string& raw_bytes = filename.value();
210#endif
211 pending_data_.append(net::GetDirectoryListingEntry(
212 filename.LossyDisplayName(), raw_bytes, data.info.IsDirectory(),
213 data.info.GetSize(), data.info.GetLastModifiedTime()));
214 }
215
216 MaybeTransferPendingData();
217 }
218
219 void OnListDone(int error) override {
220 listing_result_ = error;
221 lister_.reset();
222 MaybeDeleteSelf();
223 }
224
225 void MaybeTransferPendingData() {
226 if (transfer_in_progress_)
227 return;
228
229 transfer_in_progress_ = true;
230 data_producer_->Write(pending_data_,
Daniel Murphy0ecba612018-02-07 20:56:18231 mojo::StringDataPipeProducer::AsyncWritingMode::
232 STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
Ken Rockot314714c2017-11-05 23:36:24233 base::BindOnce(&FileURLDirectoryLoader::OnDataWritten,
234 base::Unretained(this)));
235 // The producer above will have already copied any parts of |pending_data_|
236 // that couldn't be written immediately, so we can wipe it out here to begin
237 // accumulating more data.
238 pending_data_.clear();
239 }
240
241 void OnDataWritten(MojoResult result) {
242 transfer_in_progress_ = false;
243
244 int completion_status;
245 if (result == MOJO_RESULT_OK) {
246 if (!pending_data_.empty()) {
247 // Keep flushing the data buffer as long as it's non-empty and pipe
248 // writes are succeeding.
249 MaybeTransferPendingData();
250 return;
251 }
252
253 // If there's no pending data but the lister is still active, we simply
254 // wait for more listing results.
255 if (lister_)
256 return;
257
258 // At this point we know the listing is complete and all available data
259 // has been transferred. We inherit the status of the listing operation.
260 completion_status = listing_result_;
261 } else {
262 completion_status = net::ERR_FAILED;
263 }
264
arthursonzogni3a4ca9f2017-12-07 17:58:34265 // All the data has been written now. Close the data pipe. The consumer will
266 // be notified that there will be no more data to read from now.
267 data_producer_.reset();
268
Takashi Toyoshimaaa278662017-11-20 11:11:26269 client_->OnComplete(network::URLLoaderCompletionStatus(completion_status));
Ken Rockot314714c2017-11-05 23:36:24270 client_.reset();
arthursonzogni3a4ca9f2017-12-07 17:58:34271
Ken Rockot314714c2017-11-05 23:36:24272 MaybeDeleteSelf();
273 }
274
275 base::FilePath path_;
276 std::unique_ptr<net::DirectoryLister> lister_;
277 bool wrote_header_ = false;
278 int listing_result_;
279
John Abd-El-Malekb165dc52018-01-18 17:12:18280 mojo::Binding<network::mojom::URLLoader> binding_;
281 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24282
283 std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
284 std::string pending_data_;
285 bool transfer_in_progress_ = false;
286
287 DISALLOW_COPY_AND_ASSIGN(FileURLDirectoryLoader);
288};
289
John Abd-El-Malekb165dc52018-01-18 17:12:18290class FileURLLoader : public network::mojom::URLLoader {
Ken Rockot314714c2017-11-05 23:36:24291 public:
Ken Rockota0dfaca12018-02-15 07:26:25292 static void CreateAndStart(
293 const base::FilePath& profile_path,
294 const network::ResourceRequest& request,
295 network::mojom::URLLoaderRequest loader,
296 network::mojom::URLLoaderClientPtrInfo client_info,
297 DirectoryLoadingPolicy directory_loading_policy,
298 FileAccessPolicy file_access_policy,
299 LinkFollowingPolicy link_following_policy,
300 std::unique_ptr<FileURLLoaderObserver> observer,
301 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot314714c2017-11-05 23:36:24302 // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
303 // bindings are alive - essentially until either the client gives up or all
304 // file data has been sent to it.
305 auto* file_url_loader = new FileURLLoader;
Ken Rockota0dfaca12018-02-15 07:26:25306 file_url_loader->Start(
307 profile_path, request, std::move(loader), std::move(client_info),
308 directory_loading_policy, file_access_policy, link_following_policy,
309 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24310 }
311
John Abd-El-Malekb165dc52018-01-18 17:12:18312 // network::mojom::URLLoader:
Ken Rockot314714c2017-11-05 23:36:24313 void FollowRedirect() override {}
arthursonzogni2e1524a72017-12-18 16:53:26314 void ProceedWithResponse() override {}
Ken Rockot314714c2017-11-05 23:36:24315 void SetPriority(net::RequestPriority priority,
316 int32_t intra_priority_value) override {}
317 void PauseReadingBodyFromNet() override {}
318 void ResumeReadingBodyFromNet() override {}
319
320 private:
321 FileURLLoader() : binding_(this) {}
322 ~FileURLLoader() override = default;
323
324 void Start(const base::FilePath& profile_path,
John Abd-El-Malek1df61792018-01-12 20:40:45325 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18326 network::mojom::URLLoaderRequest loader,
327 network::mojom::URLLoaderClientPtrInfo client_info,
Ken Rockot6414c4d92017-11-08 19:58:32328 DirectoryLoadingPolicy directory_loading_policy,
329 FileAccessPolicy file_access_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42330 LinkFollowingPolicy link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25331 std::unique_ptr<FileURLLoaderObserver> observer,
332 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
John Abd-El-Malek46248032018-01-17 19:11:23333 network::ResourceResponseHead head;
Ken Rockot314714c2017-11-05 23:36:24334 head.request_start = base::TimeTicks::Now();
335 head.response_start = base::TimeTicks::Now();
Ken Rockota0dfaca12018-02-15 07:26:25336 head.headers = extra_response_headers;
Ken Rockot314714c2017-11-05 23:36:24337 binding_.Bind(std::move(loader));
338 binding_.set_connection_error_handler(base::BindOnce(
339 &FileURLLoader::OnConnectionError, base::Unretained(this)));
340
John Abd-El-Malekb165dc52018-01-18 17:12:18341 network::mojom::URLLoaderClientPtr client;
Ken Rockot314714c2017-11-05 23:36:24342 client.Bind(std::move(client_info));
343
344 base::FilePath path;
345 if (!net::FileURLToFilePath(request.url, &path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26346 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42347 if (observer)
348 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24349 return;
350 }
351
352 base::File::Info info;
353 if (!base::GetFileInfo(path, &info)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26354 client->OnComplete(
355 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42356 if (observer)
357 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24358 return;
359 }
360
Ken Rockot6414c4d92017-11-08 19:58:32361 if (info.is_directory) {
362 if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26363 client->OnComplete(
364 network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
Chris Mumfordb5df8ef22017-12-20 17:47:42365 if (observer)
366 observer->OnDoneReading();
Ken Rockot6414c4d92017-11-08 19:58:32367 return;
368 }
Ken Rockot314714c2017-11-05 23:36:24369
Ken Rockot6414c4d92017-11-08 19:58:32370 DCHECK_EQ(directory_loading_policy,
371 DirectoryLoadingPolicy::kRespondWithListing);
372
373 GURL directory_url = request.url;
374 if (!path.EndsWithSeparator()) {
375 // If the named path is a directory with no trailing slash, redirect to
376 // the same path, but with a trailing slash.
377 std::string new_path = directory_url.path() + '/';
378 GURL::Replacements replacements;
379 replacements.SetPathStr(new_path);
380 directory_url = directory_url.ReplaceComponents(replacements);
381
382 net::RedirectInfo redirect_info;
383 redirect_info.new_method = "GET";
384 redirect_info.status_code = 301;
385 redirect_info.new_url = directory_url;
386 head.encoded_data_length = 0;
387 client->OnReceiveRedirect(redirect_info, head);
388 }
Ken Rockot314714c2017-11-05 23:36:24389
390 // Restart the request with a directory loader.
John Abd-El-Malek1df61792018-01-12 20:40:45391 network::ResourceRequest new_request = request;
Ken Rockot6414c4d92017-11-08 19:58:32392 new_request.url = directory_url;
Ken Rockot314714c2017-11-05 23:36:24393 FileURLDirectoryLoader::CreateAndStart(
Chris Mumfordb5df8ef22017-12-20 17:47:42394 profile_path, new_request, binding_.Unbind(), client.PassInterface(),
395 std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24396 MaybeDeleteSelf();
397 return;
398 }
399
400#if defined(OS_WIN)
401 base::FilePath shortcut_target;
Ken Rockot6414c4d92017-11-08 19:58:32402 if (link_following_policy == LinkFollowingPolicy::kFollow &&
403 base::LowerCaseEqualsASCII(path.Extension(), ".lnk") &&
Ken Rockot314714c2017-11-05 23:36:24404 base::win::ResolveShortcut(path, &shortcut_target, nullptr)) {
405 // Follow Windows shortcuts
406 GURL new_url = net::FilePathToFileURL(shortcut_target);
407
408 net::RedirectInfo redirect_info;
409 redirect_info.new_method = "GET";
410 redirect_info.status_code = 301;
411 redirect_info.new_url = new_url;
412 head.encoded_data_length = 0;
413 client->OnReceiveRedirect(redirect_info, head);
414
415 // Restart the request with the new URL.
John Abd-El-Malek1df61792018-01-12 20:40:45416 network::ResourceRequest new_request = request;
Ken Rockot314714c2017-11-05 23:36:24417 new_request.url = redirect_info.new_url;
418 return Start(profile_path, request, binding_.Unbind(),
Ken Rockot6414c4d92017-11-08 19:58:32419 client.PassInterface(), directory_loading_policy,
Chris Mumfordb5df8ef22017-12-20 17:47:42420 file_access_policy, link_following_policy,
Ken Rockota0dfaca12018-02-15 07:26:25421 std::move(observer), std::move(extra_response_headers));
Ken Rockot314714c2017-11-05 23:36:24422 }
423#endif // defined(OS_WIN)
424
Ken Rockot6414c4d92017-11-08 19:58:32425 if (file_access_policy == FileAccessPolicy::kRestricted &&
426 !GetContentClient()->browser()->IsFileAccessAllowed(
Ken Rockot314714c2017-11-05 23:36:24427 path, base::MakeAbsoluteFilePath(path), profile_path)) {
Takashi Toyoshimaaa278662017-11-20 11:11:26428 client->OnComplete(
429 network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
Chris Mumfordb5df8ef22017-12-20 17:47:42430 if (observer)
431 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24432 return;
433 }
434
435 mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
436 if (!pipe.consumer_handle.is_valid()) {
Takashi Toyoshimaaa278662017-11-20 11:11:26437 client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Chris Mumfordb5df8ef22017-12-20 17:47:42438 if (observer)
439 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24440 return;
441 }
442
443 // Should never be possible for this to be a directory. If FileURLLoader
444 // is used to respond to a directory request, it must be because the URL
445 // path didn't have a trailing path separator. In that case we finish with
446 // a redirect above which will in turn be handled by FileURLDirectoryLoader.
447 DCHECK(!info.is_directory);
Chris Mumfordb5df8ef22017-12-20 17:47:42448 if (observer)
449 observer->OnStart();
Ken Rockot314714c2017-11-05 23:36:24450
451 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
Chris Mumfordb5df8ef22017-12-20 17:47:42452 if (observer)
453 observer->OnOpenComplete(net::FileErrorToNetError(file.error_details()));
Ken Rockot314714c2017-11-05 23:36:24454 char initial_read_buffer[net::kMaxBytesToSniff];
455 int initial_read_result =
456 file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
457 if (initial_read_result < 0) {
Chris Mumfordb5df8ef22017-12-20 17:47:42458 base::File::Error read_error = base::File::GetLastFileError();
459 DCHECK_NE(base::File::FILE_OK, read_error);
460 if (observer) {
461 observer->OnBytesRead(nullptr, 0u, read_error);
462 observer->OnDoneReading();
463 }
464 net::Error net_error = net::FileErrorToNetError(read_error);
465 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
Ken Rockot314714c2017-11-05 23:36:24466 return;
Chris Mumfordb5df8ef22017-12-20 17:47:42467 } else if (observer) {
468 observer->OnBytesRead(initial_read_buffer, initial_read_result,
469 base::File::FILE_OK);
Ken Rockot314714c2017-11-05 23:36:24470 }
471 size_t initial_read_size = static_cast<size_t>(initial_read_result);
472
473 std::string range_header;
474 net::HttpByteRange byte_range;
475 if (request.headers.GetHeader(net::HttpRequestHeaders::kRange,
476 &range_header)) {
477 // Handle a simple Range header for a single range.
478 std::vector<net::HttpByteRange> ranges;
479 bool fail = false;
480 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
481 ranges.size() == 1) {
482 byte_range = ranges[0];
483 if (!byte_range.ComputeBounds(info.size))
484 fail = true;
485 } else {
486 fail = true;
487 }
488
489 if (fail) {
Takashi Toyoshimaaa278662017-11-20 11:11:26490 client->OnComplete(network::URLLoaderCompletionStatus(
491 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
Chris Mumfordb5df8ef22017-12-20 17:47:42492 if (observer)
493 observer->OnDoneReading();
Ken Rockot314714c2017-11-05 23:36:24494 return;
495 }
496 }
497
498 size_t first_byte_to_send = 0;
499 size_t total_bytes_to_send = static_cast<size_t>(info.size);
500 if (byte_range.IsValid()) {
501 first_byte_to_send =
502 static_cast<size_t>(byte_range.first_byte_position());
503 total_bytes_to_send =
504 static_cast<size_t>(byte_range.last_byte_position()) -
505 first_byte_to_send + 1;
506 }
507
508 head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
509
510 if (first_byte_to_send < initial_read_size) {
511 // Write any data we read for MIME sniffing, constraining by range where
512 // applicable. This will always fit in the pipe (see assertion near
513 // |kDefaultFileUrlPipeSize| definition).
514 uint32_t write_size = std::min(
515 static_cast<uint32_t>(initial_read_size - first_byte_to_send),
516 static_cast<uint32_t>(total_bytes_to_send));
517 const uint32_t expected_write_size = write_size;
518 MojoResult result = pipe.producer_handle->WriteData(
519 &initial_read_buffer[first_byte_to_send], &write_size,
520 MOJO_WRITE_DATA_FLAG_NONE);
521 if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
Chris Mumfordb5df8ef22017-12-20 17:47:42522 OnFileWritten(std::move(observer), result);
Ken Rockot314714c2017-11-05 23:36:24523 return;
524 }
525
526 // Discount the bytes we just sent from the total range.
527 first_byte_to_send = initial_read_size;
528 total_bytes_to_send -= write_size;
529 }
530
531 if (!net::GetMimeTypeFromFile(path, &head.mime_type)) {
Matt Menkefcbb1bd72018-01-31 21:53:12532 net::SniffMimeType(
533 initial_read_buffer, initial_read_result, request.url, head.mime_type,
534 GetContentClient()->browser()->ForceSniffingFileUrlsForHtml()
535 ? net::ForceSniffFileUrlsForHtml::kEnabled
536 : net::ForceSniffFileUrlsForHtml::kDisabled,
537 &head.mime_type);
Ken Rockot314714c2017-11-05 23:36:24538 }
539 client->OnReceiveResponse(head, base::nullopt, nullptr);
540 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
541 client_ = std::move(client);
542
543 if (total_bytes_to_send == 0) {
544 // There's definitely no more data, so we're already done.
Chris Mumfordb5df8ef22017-12-20 17:47:42545 OnFileWritten(std::move(observer), MOJO_RESULT_OK);
Ken Rockot314714c2017-11-05 23:36:24546 return;
547 }
548
549 // In case of a range request, seek to the appropriate position before
550 // sending the remaining bytes asynchronously. Under normal conditions
551 // (i.e., no range request) this Seek is effectively a no-op.
Chris Mumfordb5df8ef22017-12-20 17:47:42552 int new_position = file.Seek(base::File::FROM_BEGIN,
553 static_cast<int64_t>(first_byte_to_send));
554 if (observer)
555 observer->OnSeekComplete(new_position);
Ken Rockot314714c2017-11-05 23:36:24556
557 data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
Chris Mumfordb5df8ef22017-12-20 17:47:42558 std::move(pipe.producer_handle), std::move(observer));
Ken Rockot314714c2017-11-05 23:36:24559 data_producer_->WriteFromFile(
560 std::move(file), total_bytes_to_send,
Chris Mumfordb5df8ef22017-12-20 17:47:42561 base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
562 nullptr));
Ken Rockot314714c2017-11-05 23:36:24563 }
564
565 void OnConnectionError() {
566 binding_.Close();
567 MaybeDeleteSelf();
568 }
569
570 void MaybeDeleteSelf() {
571 if (!binding_.is_bound() && !client_.is_bound())
572 delete this;
573 }
574
Chris Mumfordb5df8ef22017-12-20 17:47:42575 void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
576 MojoResult result) {
arthursonzogni3a4ca9f2017-12-07 17:58:34577 // All the data has been written now. Close the data pipe. The consumer will
578 // be notified that there will be no more data to read from now.
579 data_producer_.reset();
Chris Mumfordb5df8ef22017-12-20 17:47:42580 if (observer)
581 observer->OnDoneReading();
arthursonzogni3a4ca9f2017-12-07 17:58:34582
Ken Rockot314714c2017-11-05 23:36:24583 if (result == MOJO_RESULT_OK)
Takashi Toyoshimaaa278662017-11-20 11:11:26584 client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
Ken Rockot314714c2017-11-05 23:36:24585 else
Takashi Toyoshimaaa278662017-11-20 11:11:26586 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
Ken Rockot314714c2017-11-05 23:36:24587 client_.reset();
588 MaybeDeleteSelf();
589 }
590
591 std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
John Abd-El-Malekb165dc52018-01-18 17:12:18592 mojo::Binding<network::mojom::URLLoader> binding_;
593 network::mojom::URLLoaderClientPtr client_;
Ken Rockot314714c2017-11-05 23:36:24594
595 DISALLOW_COPY_AND_ASSIGN(FileURLLoader);
596};
597
598} // namespace
599
600FileURLLoaderFactory::FileURLLoaderFactory(
601 const base::FilePath& profile_path,
602 scoped_refptr<base::SequencedTaskRunner> task_runner)
603 : profile_path_(profile_path), task_runner_(std::move(task_runner)) {}
604
605FileURLLoaderFactory::~FileURLLoaderFactory() = default;
606
Ken Rockot314714c2017-11-05 23:36:24607void FileURLLoaderFactory::CreateLoaderAndStart(
John Abd-El-Malekb165dc52018-01-18 17:12:18608 network::mojom::URLLoaderRequest loader,
Ken Rockot314714c2017-11-05 23:36:24609 int32_t routing_id,
610 int32_t request_id,
611 uint32_t options,
John Abd-El-Malek1df61792018-01-12 20:40:45612 const network::ResourceRequest& request,
John Abd-El-Malekb165dc52018-01-18 17:12:18613 network::mojom::URLLoaderClientPtr client,
Ken Rockot314714c2017-11-05 23:36:24614 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
615 base::FilePath file_path;
616 const bool is_file = net::FileURLToFilePath(request.url, &file_path);
617 if (is_file && file_path.EndsWithSeparator() && file_path.IsAbsolute()) {
618 task_runner_->PostTask(
619 FROM_HERE,
620 base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
Chris Mumfordb5df8ef22017-12-20 17:47:42621 request, std::move(loader), client.PassInterface(),
622 std::unique_ptr<FileURLLoaderObserver>()));
Ken Rockot314714c2017-11-05 23:36:24623 } else {
624 task_runner_->PostTask(
625 FROM_HERE,
626 base::BindOnce(&FileURLLoader::CreateAndStart, profile_path_, request,
Ken Rockot6414c4d92017-11-08 19:58:32627 std::move(loader), client.PassInterface(),
628 DirectoryLoadingPolicy::kRespondWithListing,
629 FileAccessPolicy::kRestricted,
Chris Mumfordb5df8ef22017-12-20 17:47:42630 LinkFollowingPolicy::kFollow,
Ken Rockota0dfaca12018-02-15 07:26:25631 std::unique_ptr<FileURLLoaderObserver>(),
632 nullptr /* extra_response_headers */));
Ken Rockot314714c2017-11-05 23:36:24633 }
634}
635
John Abd-El-Malekb165dc52018-01-18 17:12:18636void FileURLLoaderFactory::Clone(
637 network::mojom::URLLoaderFactoryRequest loader) {
Ken Rockot6414c4d92017-11-08 19:58:32638 bindings_.AddBinding(this, std::move(loader));
639}
640
Ken Rockota0dfaca12018-02-15 07:26:25641void CreateFileURLLoader(
642 const network::ResourceRequest& request,
643 network::mojom::URLLoaderRequest loader,
644 network::mojom::URLLoaderClientPtr client,
645 std::unique_ptr<FileURLLoaderObserver> observer,
646 scoped_refptr<net::HttpResponseHeaders> extra_response_headers) {
Ken Rockot6414c4d92017-11-08 19:58:32647 auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
648 {base::MayBlock(), base::TaskPriority::BACKGROUND,
649 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
650 task_runner->PostTask(
651 FROM_HERE,
652 base::BindOnce(&FileURLLoader::CreateAndStart, base::FilePath(), request,
653 std::move(loader), client.PassInterface(),
654 DirectoryLoadingPolicy::kFail,
655 FileAccessPolicy::kUnrestricted,
Ken Rockota0dfaca12018-02-15 07:26:25656 LinkFollowingPolicy::kDoNotFollow, std::move(observer),
657 std::move(extra_response_headers)));
Ken Rockot314714c2017-11-05 23:36:24658}
659
660} // namespace content