content_directory_loader_factory.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. // Copyright 2019 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. #include "fuchsia_web/webengine/browser/content_directory_loader_factory.h"
  5. #include <lib/fdio/directory.h>
  6. #include <lib/fdio/fdio.h>
  7. #include <algorithm>
  8. #include <memory>
  9. #include <utility>
  10. #include "base/command_line.h"
  11. #include "base/files/memory_mapped_file.h"
  12. #include "base/fuchsia/fuchsia_logging.h"
  13. #include "base/json/json_reader.h"
  14. #include "base/lazy_instance.h"
  15. #include "base/logging.h"
  16. #include "base/strings/strcat.h"
  17. #include "base/strings/string_piece.h"
  18. #include "base/strings/string_split.h"
  19. #include "base/strings/string_util.h"
  20. #include "base/task/task_traits.h"
  21. #include "base/task/thread_pool.h"
  22. #include "fuchsia_web/common/fuchsia_dir_scheme.h"
  23. #include "fuchsia_web/webengine/common/web_engine_content_client.h"
  24. #include "fuchsia_web/webengine/switches.h"
  25. #include "mojo/public/cpp/bindings/remote.h"
  26. #include "mojo/public/cpp/bindings/self_owned_receiver.h"
  27. #include "mojo/public/cpp/system/data_pipe_producer.h"
  28. #include "mojo/public/cpp/system/string_data_source.h"
  29. #include "net/base/filename_util.h"
  30. #include "net/base/mime_sniffer.h"
  31. #include "net/base/parse_number.h"
  32. #include "net/http/http_byte_range.h"
  33. #include "net/http/http_util.h"
  34. #include "services/network/public/cpp/resource_request.h"
  35. #include "services/network/public/mojom/url_loader.mojom.h"
  36. #include "services/network/public/mojom/url_response_head.mojom.h"
  37. namespace {
  38. // Maximum number of bytes to read when "sniffing" its MIME type.
  39. constexpr size_t kMaxBytesToSniff = 1024 * 10; // Read up to 10KB.
  40. // The MIME type to use if "sniffing" fails to compute a result.
  41. constexpr char kFallbackMimeType[] = "application/octet-stream";
  42. // Returns a list of populated response HTTP headers.
  43. // |mime_type|: The MIME type of the resource.
  44. // |charset|: The resource's character set. Optional. If omitted, the browser
  45. // will assume the charset to be "text/plain" by default.
  46. scoped_refptr<net::HttpResponseHeaders> CreateHeaders(
  47. base::StringPiece mime_type,
  48. const absl::optional<std::string>& charset) {
  49. constexpr char kXFrameOptions[] = "X-Frame-Options";
  50. constexpr char kXFrameOptionsValue[] = "DENY";
  51. constexpr char kCacheControl[] = "Cache-Control";
  52. constexpr char kCacheControlValue[] = "no-cache";
  53. constexpr char kContentType[] = "Content-Type";
  54. constexpr char kCharsetSeparator[] = "; charset=";
  55. auto headers =
  56. base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\r\n");
  57. headers->SetHeader(kXFrameOptions, kXFrameOptionsValue);
  58. headers->SetHeader(kCacheControl, kCacheControlValue);
  59. if (charset) {
  60. headers->SetHeader(kContentType,
  61. base::StrCat({mime_type, kCharsetSeparator, *charset}));
  62. } else {
  63. headers->SetHeader(kContentType, mime_type);
  64. }
  65. return headers;
  66. }
  67. // Determines which range of bytes should be sent in the response.
  68. // If a range is specified in |headers|, then |start| and |length| are set to
  69. // the range's boundaries and the function returns true.
  70. // If no range is specified in |headers|, then the entire range [0,
  71. // |max_length|) is set and the function returns true.
  72. // If the requested range is invalid, then the function returns false.
  73. bool GetRangeForRequest(const net::HttpRequestHeaders& headers,
  74. size_t max_length,
  75. size_t* start,
  76. size_t* length) {
  77. std::string range_header;
  78. net::HttpByteRange byte_range;
  79. if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
  80. std::vector<net::HttpByteRange> ranges;
  81. if (net::HttpUtil::ParseRangeHeader(range_header, &ranges) &&
  82. ranges.size() == 1) {
  83. byte_range = ranges[0];
  84. } else {
  85. // Only one range is allowed.
  86. return false;
  87. }
  88. }
  89. if (!byte_range.ComputeBounds(max_length)) {
  90. return false;
  91. }
  92. *start = byte_range.first_byte_position();
  93. *length =
  94. byte_range.last_byte_position() - byte_range.first_byte_position() + 1;
  95. return true;
  96. }
  97. // Copies data from a fuchsia.io.Node file into a URL response stream.
  98. class ContentDirectoryURLLoader final : public network::mojom::URLLoader {
  99. public:
  100. ContentDirectoryURLLoader() = default;
  101. ~ContentDirectoryURLLoader() override = default;
  102. ContentDirectoryURLLoader(const ContentDirectoryURLLoader&) = delete;
  103. ContentDirectoryURLLoader& operator=(const ContentDirectoryURLLoader&) =
  104. delete;
  105. // Creates a read-only MemoryMappedFile view to |file|.
  106. bool MapFile(fidl::InterfaceHandle<fuchsia::io::Node> file,
  107. base::MemoryMappedFile* mmap) {
  108. // Bind the file channel to a FDIO entry and then a file descriptor so that
  109. // we can use it for reading.
  110. fdio_t* fdio = nullptr;
  111. zx_status_t status = fdio_create(file.TakeChannel().release(), &fdio);
  112. if (status == ZX_ERR_PEER_CLOSED) {
  113. // File-not-found errors are expected in some cases, so handle this result
  114. // w/o logging error text.
  115. return false;
  116. } else if (status != ZX_OK) {
  117. ZX_DLOG_IF(WARNING, status != ZX_OK, status) << "fdio_create";
  118. return false;
  119. }
  120. base::ScopedFD fd(fdio_bind_to_fd(fdio, -1, 0));
  121. if (!fd.is_valid()) {
  122. LOG(ERROR) << "fdio_bind_to_fd returned an invalid FD.";
  123. return false;
  124. }
  125. // Map the file into memory.
  126. if (!mmap->Initialize(base::File(std::move(fd)),
  127. base::MemoryMappedFile::READ_ONLY)) {
  128. return false;
  129. }
  130. return true;
  131. }
  132. // Initiates data transfer from |file_channel| to |client_remote|.
  133. // |metadata_channel|, if it is connected to a file, is accessed to get the
  134. // MIME type and charset of the file.
  135. static void CreateAndStart(
  136. mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
  137. const network::ResourceRequest& request,
  138. mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
  139. fidl::InterfaceHandle<fuchsia::io::Node> file_channel,
  140. fidl::InterfaceHandle<fuchsia::io::Node> metadata_channel) {
  141. std::unique_ptr<ContentDirectoryURLLoader> loader =
  142. std::make_unique<ContentDirectoryURLLoader>();
  143. loader->Start(request, std::move(client_remote), std::move(file_channel),
  144. std::move(metadata_channel));
  145. // |loader|'s lifetime is bound to the lifetime of the URLLoader Mojo
  146. // client endpoint.
  147. mojo::MakeSelfOwnedReceiver(std::move(loader),
  148. std::move(url_loader_receiver));
  149. }
  150. void Start(const network::ResourceRequest& request,
  151. mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
  152. fidl::InterfaceHandle<fuchsia::io::Node> file_channel,
  153. fidl::InterfaceHandle<fuchsia::io::Node> metadata_channel) {
  154. client_.Bind(std::move(client_remote));
  155. if (!MapFile(std::move(file_channel), &mmap_)) {
  156. client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
  157. return;
  158. }
  159. // Construct and deliver the HTTP response header.
  160. auto response = network::mojom::URLResponseHead::New();
  161. // Read the charset and MIME type from the optional _metadata file.
  162. absl::optional<std::string> charset;
  163. absl::optional<std::string> mime_type;
  164. base::MemoryMappedFile metadata_mmap;
  165. if (MapFile(std::move(metadata_channel), &metadata_mmap)) {
  166. absl::optional<base::Value> metadata_parsed = base::JSONReader::Read(
  167. base::StringPiece(reinterpret_cast<char*>(metadata_mmap.data()),
  168. metadata_mmap.length()));
  169. if (metadata_parsed && metadata_parsed->is_dict()) {
  170. const auto& dict = metadata_parsed->GetDict();
  171. const std::string* parsed_charset = dict.FindString("charset");
  172. if (parsed_charset)
  173. charset = *parsed_charset;
  174. const std::string* parsed_mime = dict.FindString("mime");
  175. if (parsed_mime)
  176. mime_type = *parsed_mime;
  177. }
  178. }
  179. // If a MIME type wasn't specified, then fall back on inferring the type
  180. // from the file's contents.
  181. if (!mime_type) {
  182. if (!net::SniffMimeType(
  183. base::StringPiece(reinterpret_cast<char*>(mmap_.data()),
  184. std::min(mmap_.length(), kMaxBytesToSniff)),
  185. request.url, {} /* type_hint */,
  186. net::ForceSniffFileUrlsForHtml::kDisabled,
  187. &mime_type.emplace())) {
  188. if (!mime_type) {
  189. // Only set the fallback type if SniffMimeType completely gave up on
  190. // generating a suggestion.
  191. *mime_type = kFallbackMimeType;
  192. }
  193. }
  194. }
  195. size_t start_offset;
  196. size_t content_length;
  197. if (!GetRangeForRequest(request.headers, mmap_.length(), &start_offset,
  198. &content_length)) {
  199. client_->OnComplete(network::URLLoaderCompletionStatus(
  200. net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
  201. return;
  202. }
  203. response->mime_type = *mime_type;
  204. response->headers = CreateHeaders(*mime_type, charset);
  205. response->content_length = content_length;
  206. // Set up the Mojo DataPipe used for streaming the response payload to the
  207. // client.
  208. mojo::ScopedDataPipeProducerHandle producer_handle;
  209. mojo::ScopedDataPipeConsumerHandle consumer_handle;
  210. MojoResult rv = mojo::CreateDataPipe(0u, producer_handle, consumer_handle);
  211. if (rv != MOJO_RESULT_OK) {
  212. client_->OnComplete(
  213. network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
  214. return;
  215. }
  216. client_->OnReceiveResponse(std::move(response), std::move(consumer_handle));
  217. // Start reading the contents of |mmap_| into the response DataPipe.
  218. body_writer_ =
  219. std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle));
  220. body_writer_->Write(
  221. std::make_unique<mojo::StringDataSource>(
  222. base::StringPiece(
  223. reinterpret_cast<char*>(mmap_.data() + start_offset),
  224. content_length),
  225. mojo::StringDataSource::AsyncWritingMode::
  226. STRING_STAYS_VALID_UNTIL_COMPLETION),
  227. base::BindOnce(&ContentDirectoryURLLoader::OnWriteComplete,
  228. base::Unretained(this)));
  229. }
  230. // network::mojom::URLLoader implementation:
  231. void FollowRedirect(
  232. const std::vector<std::string>& removed_headers,
  233. const net::HttpRequestHeaders& modified_request_headers,
  234. const net::HttpRequestHeaders& modified_cors_exempt_request_headers,
  235. const absl::optional<GURL>& new_url) override {}
  236. void SetPriority(net::RequestPriority priority,
  237. int32_t intra_priority_value) override {}
  238. void PauseReadingBodyFromNet() override {}
  239. void ResumeReadingBodyFromNet() override {}
  240. private:
  241. // Called when body_writer_->Write() has completed asynchronously.
  242. void OnWriteComplete(MojoResult result) {
  243. // Signal stream EOF to the client.
  244. body_writer_.reset();
  245. if (result != MOJO_RESULT_OK) {
  246. client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
  247. return;
  248. }
  249. network::URLLoaderCompletionStatus status(net::OK);
  250. status.encoded_data_length = mmap_.length();
  251. status.encoded_body_length = mmap_.length();
  252. status.decoded_body_length = mmap_.length();
  253. client_->OnComplete(std::move(status));
  254. }
  255. // Used for sending status codes and response payloads to the client.
  256. mojo::Remote<network::mojom::URLLoaderClient> client_;
  257. // A read-only, memory mapped view of the file being loaded.
  258. base::MemoryMappedFile mmap_;
  259. // Manages chunked data transfer over the response DataPipe.
  260. std::unique_ptr<mojo::DataPipeProducer> body_writer_;
  261. };
  262. net::Error OpenFileFromDirectory(
  263. const std::string& content_directory_name,
  264. const base::FilePath& relative_file_path,
  265. fidl::InterfaceRequest<fuchsia::io::Node> file_request) {
  266. DCHECK(file_request);
  267. DCHECK(!relative_file_path.IsAbsolute());
  268. auto absolute_file_path =
  269. base::FilePath(ContentDirectoryLoaderFactory::kContentDirectoriesPath)
  270. .Append(content_directory_name)
  271. .Append(relative_file_path);
  272. const zx_status_t status =
  273. fdio_open(absolute_file_path.value().c_str(),
  274. static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE),
  275. file_request.TakeChannel().release());
  276. if (status != ZX_OK) {
  277. ZX_DLOG(WARNING, status) << "fdio_open";
  278. return net::ERR_FILE_NOT_FOUND;
  279. }
  280. return net::OK;
  281. }
  282. } // namespace
  283. // static
  284. const char ContentDirectoryLoaderFactory::kContentDirectoriesPath[] =
  285. "/content-directories";
  286. // static
  287. mojo::PendingRemote<network::mojom::URLLoaderFactory>
  288. ContentDirectoryLoaderFactory::Create() {
  289. mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
  290. // The ContentDirectoryLoaderFactory will delete itself when there are no more
  291. // receivers - see the network::SelfDeletingURLLoaderFactory::OnDisconnect
  292. // method.
  293. new ContentDirectoryLoaderFactory(
  294. pending_remote.InitWithNewPipeAndPassReceiver());
  295. return pending_remote;
  296. }
  297. ContentDirectoryLoaderFactory::ContentDirectoryLoaderFactory(
  298. mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver)
  299. : network::SelfDeletingURLLoaderFactory(std::move(factory_receiver)),
  300. task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
  301. {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
  302. base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})) {}
  303. ContentDirectoryLoaderFactory::~ContentDirectoryLoaderFactory() = default;
  304. void ContentDirectoryLoaderFactory::CreateLoaderAndStart(
  305. mojo::PendingReceiver<network::mojom::URLLoader> loader,
  306. int32_t request_id,
  307. uint32_t options,
  308. const network::ResourceRequest& request,
  309. mojo::PendingRemote<network::mojom::URLLoaderClient> client,
  310. const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
  311. if (!request.url.SchemeIs(kFuchsiaDirScheme) || !request.url.is_valid()) {
  312. mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
  313. ->OnComplete(network::URLLoaderCompletionStatus(net::ERR_INVALID_URL));
  314. return;
  315. }
  316. if (request.method != "GET") {
  317. mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
  318. ->OnComplete(
  319. network::URLLoaderCompletionStatus(net::ERR_METHOD_NOT_SUPPORTED));
  320. return;
  321. }
  322. // Fuchsia paths do not support the notion of absolute paths, so strip the
  323. // leading slash from the URL's path fragment.
  324. base::StringPiece requested_path = request.url.path_piece();
  325. DCHECK(base::StartsWith(requested_path, "/"));
  326. requested_path.remove_prefix(1);
  327. fidl::InterfaceHandle<fuchsia::io::Node> file_handle;
  328. net::Error open_result = OpenFileFromDirectory(
  329. request.url.DeprecatedGetOriginAsURL().host(),
  330. base::FilePath(requested_path), file_handle.NewRequest());
  331. if (open_result != net::OK) {
  332. mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
  333. ->OnComplete(network::URLLoaderCompletionStatus(open_result));
  334. return;
  335. }
  336. DCHECK(file_handle);
  337. // Metadata files are optional. In the event that the file is absent,
  338. // |metadata_channel| will produce a ZX_CHANNEL_PEER_CLOSED status inside
  339. // ContentDirectoryURLLoader::Start().
  340. fidl::InterfaceHandle<fuchsia::io::Node> metadata_handle;
  341. open_result = OpenFileFromDirectory(
  342. request.url.DeprecatedGetOriginAsURL().host(),
  343. base::FilePath(base::StrCat({requested_path, "._metadata"})),
  344. metadata_handle.NewRequest());
  345. if (open_result != net::OK) {
  346. mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
  347. ->OnComplete(network::URLLoaderCompletionStatus(open_result));
  348. return;
  349. }
  350. // Load the resource on a blocking-capable TaskRunner.
  351. task_runner_->PostTask(
  352. FROM_HERE,
  353. base::BindOnce(&ContentDirectoryURLLoader::CreateAndStart,
  354. std::move(loader), request, std::move(client),
  355. std::move(file_handle), std::move(metadata_handle)));
  356. }