web_bundle_url_loader_factory.cc 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. // Copyright 2020 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 "components/web_package/web_bundle_url_loader_factory.h"
  5. #include "base/metrics/histogram_functions.h"
  6. #include "base/ranges/algorithm.h"
  7. #include "base/threading/sequenced_task_runner_handle.h"
  8. #include "base/time/time.h"
  9. #include "base/trace_event/trace_event.h"
  10. #include "components/web_package/web_bundle_chunked_buffer.h"
  11. #include "components/web_package/web_bundle_memory_quota_consumer.h"
  12. #include "components/web_package/web_bundle_parser.h"
  13. #include "components/web_package/web_bundle_utils.h"
  14. #include "mojo/public/cpp/bindings/self_owned_receiver.h"
  15. #include "mojo/public/cpp/system/data_pipe.h"
  16. #include "mojo/public/cpp/system/data_pipe_drainer.h"
  17. #include "mojo/public/cpp/system/data_pipe_producer.h"
  18. #include "net/http/http_status_code.h"
  19. #include "services/network/public/cpp/corb/corb_api.h"
  20. #include "services/network/public/cpp/cors/cors.h"
  21. #include "services/network/public/cpp/cross_origin_resource_policy.h"
  22. #include "services/network/public/cpp/resource_request.h"
  23. #include "services/network/public/mojom/early_hints.mojom.h"
  24. #include "services/network/public/mojom/http_raw_headers.mojom.h"
  25. #include "services/network/public/mojom/url_loader.mojom.h"
  26. #include "services/network/public/mojom/url_response_head.mojom.h"
  27. #include "third_party/abseil-cpp/absl/types/optional.h"
  28. namespace web_package {
  29. namespace {
  30. constexpr size_t kBlockedBodyAllocationSize = 1;
  31. void DeleteProducerAndRunCallback(
  32. std::unique_ptr<mojo::DataPipeProducer> producer,
  33. base::OnceCallback<void(MojoResult result)> callback,
  34. MojoResult result) {
  35. std::move(callback).Run(result);
  36. }
  37. // Verify the serving constraints of Web Bundle HTTP responses.
  38. // https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-serving-constraints
  39. bool CheckWebBundleServingConstraints(
  40. const network::mojom::URLResponseHead& response_head,
  41. std::string& out_error_message) {
  42. if (!response_head.headers ||
  43. !network::cors::IsOkStatus(response_head.headers->response_code())) {
  44. out_error_message = "Failed to fetch Web Bundle.";
  45. return false;
  46. }
  47. if (response_head.mime_type != "application/webbundle") {
  48. out_error_message =
  49. "Web Bundle response must have \"application/webbundle\" content-type.";
  50. return false;
  51. }
  52. if (!web_package::HasNoSniffHeader(response_head)) {
  53. out_error_message =
  54. "Web Bundle response must have \"X-Content-Type-Options: nosniff\" "
  55. "header.";
  56. return false;
  57. }
  58. return true;
  59. }
  60. // URLLoaderClient which wraps the real URLLoaderClient.
  61. class WebBundleURLLoaderClient : public network::mojom::URLLoaderClient {
  62. public:
  63. WebBundleURLLoaderClient(
  64. base::WeakPtr<WebBundleURLLoaderFactory> factory,
  65. mojo::PendingRemote<network::mojom::URLLoaderClient> wrapped)
  66. : factory_(factory), wrapped_(std::move(wrapped)) {}
  67. private:
  68. mojo::ScopedDataPipeConsumerHandle HandleReceiveBody(
  69. mojo::ScopedDataPipeConsumerHandle body) {
  70. if (factory_)
  71. factory_->SetBundleStream(std::move(body));
  72. // Send empty body to the wrapped URLLoaderClient.
  73. MojoCreateDataPipeOptions options;
  74. options.struct_size = sizeof(MojoCreateDataPipeOptions);
  75. options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
  76. options.element_num_bytes = 1;
  77. options.capacity_num_bytes = kBlockedBodyAllocationSize;
  78. mojo::ScopedDataPipeProducerHandle producer;
  79. mojo::ScopedDataPipeConsumerHandle consumer;
  80. MojoResult result = mojo::CreateDataPipe(&options, producer, consumer);
  81. if (result != MOJO_RESULT_OK) {
  82. wrapped_->OnComplete(
  83. network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
  84. completed_ = true;
  85. return mojo::ScopedDataPipeConsumerHandle();
  86. }
  87. return consumer;
  88. }
  89. // network::mojom::URLLoaderClient implementation:
  90. void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override {
  91. wrapped_->OnReceiveEarlyHints(std::move(early_hints));
  92. }
  93. void OnReceiveResponse(network::mojom::URLResponseHeadPtr response_head,
  94. mojo::ScopedDataPipeConsumerHandle body) override {
  95. std::string error_message;
  96. if (!CheckWebBundleServingConstraints(*response_head, error_message)) {
  97. if (factory_) {
  98. factory_->ReportErrorAndCancelPendingLoaders(
  99. WebBundleURLLoaderFactory::SubresourceWebBundleLoadResult::
  100. kServingConstraintsNotMet,
  101. network::mojom::WebBundleErrorType::kServingConstraintsNotMet,
  102. error_message);
  103. }
  104. }
  105. base::UmaHistogramCustomCounts(
  106. "SubresourceWebBundles.ContentLength",
  107. response_head->content_length < 0 ? 0 : response_head->content_length,
  108. 1, 50000000, 50);
  109. mojo::ScopedDataPipeConsumerHandle consumer;
  110. if (body)
  111. consumer = HandleReceiveBody(std::move(body));
  112. wrapped_->OnReceiveResponse(std::move(response_head), std::move(consumer));
  113. }
  114. void OnReceiveRedirect(
  115. const net::RedirectInfo& redirect_info,
  116. network::mojom::URLResponseHeadPtr response_head) override {
  117. // TODO(crbug.com/1242281): Support redirection for WebBundle requests.
  118. if (factory_) {
  119. factory_->ReportErrorAndCancelPendingLoaders(
  120. WebBundleURLLoaderFactory::SubresourceWebBundleLoadResult::
  121. kWebBundleRedirected,
  122. network::mojom::WebBundleErrorType::kWebBundleRedirected,
  123. "URL redirection of Subresource Web Bundles is currently not "
  124. "supported.");
  125. }
  126. wrapped_->OnComplete(
  127. network::URLLoaderCompletionStatus(net::ERR_INVALID_WEB_BUNDLE));
  128. completed_ = true;
  129. }
  130. void OnUploadProgress(int64_t current_position,
  131. int64_t total_size,
  132. OnUploadProgressCallback ack_callback) override {
  133. wrapped_->OnUploadProgress(current_position, total_size,
  134. std::move(ack_callback));
  135. }
  136. void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override {
  137. wrapped_->OnReceiveCachedMetadata(std::move(data));
  138. }
  139. void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
  140. wrapped_->OnTransferSizeUpdated(transfer_size_diff);
  141. }
  142. void OnComplete(const network::URLLoaderCompletionStatus& status) override {
  143. if (status.error_code != net::OK) {
  144. if (factory_)
  145. factory_->OnWebBundleFetchFailed();
  146. base::UmaHistogramSparse("SubresourceWebBundles.BundleFetchErrorCode",
  147. -status.error_code);
  148. }
  149. if (completed_)
  150. return;
  151. wrapped_->OnComplete(status);
  152. }
  153. base::WeakPtr<WebBundleURLLoaderFactory> factory_;
  154. mojo::Remote<network::mojom::URLLoaderClient> wrapped_;
  155. bool completed_ = false;
  156. };
  157. } // namespace
  158. class WebBundleURLLoaderFactory::URLLoader : public network::mojom::URLLoader {
  159. public:
  160. URLLoader(
  161. mojo::PendingReceiver<network::mojom::URLLoader> loader,
  162. const network::ResourceRequest& request,
  163. mojo::PendingRemote<network::mojom::URLLoaderClient> client,
  164. mojo::Remote<network::mojom::TrustedHeaderClient> trusted_header_client,
  165. base::Time request_start_time,
  166. base::TimeTicks request_start_time_ticks)
  167. : url_(request.url),
  168. request_mode_(request.mode),
  169. request_initiator_(request.request_initiator),
  170. request_destination_(request.destination),
  171. devtools_request_id_(request.devtools_request_id),
  172. is_trusted_(request.trusted_params),
  173. receiver_(this, std::move(loader)),
  174. client_(std::move(client)),
  175. trusted_header_client_(std::move(trusted_header_client)) {
  176. receiver_.set_disconnect_handler(
  177. base::BindOnce(&URLLoader::OnMojoDisconnect, GetWeakPtr()));
  178. if (trusted_header_client_) {
  179. trusted_header_client_.set_disconnect_handler(
  180. base::BindOnce(&URLLoader::OnMojoDisconnect, GetWeakPtr()));
  181. }
  182. load_timing_.request_start_time = request_start_time;
  183. load_timing_.request_start = request_start_time_ticks;
  184. load_timing_.send_start = request_start_time_ticks;
  185. load_timing_.send_end = request_start_time_ticks;
  186. }
  187. URLLoader(const URLLoader&) = delete;
  188. URLLoader& operator=(const URLLoader&) = delete;
  189. const GURL& url() const { return url_; }
  190. const network::mojom::RequestMode& request_mode() const {
  191. return request_mode_;
  192. }
  193. const absl::optional<std::string>& devtools_request_id() const {
  194. return devtools_request_id_;
  195. }
  196. const absl::optional<url::Origin>& request_initiator() const {
  197. return request_initiator_;
  198. }
  199. network::mojom::RequestDestination request_destination() const {
  200. return request_destination_;
  201. }
  202. base::WeakPtr<URLLoader> GetWeakPtr() {
  203. return weak_ptr_factory_.GetWeakPtr();
  204. }
  205. void OnResponse(network::mojom::URLResponseHeadPtr response,
  206. mojo::ScopedDataPipeConsumerHandle consumer) {
  207. client_->OnReceiveResponse(std::move(response), std::move(consumer));
  208. }
  209. void OnFail(net::Error error) {
  210. client_->OnComplete(network::URLLoaderCompletionStatus(error));
  211. delete this;
  212. }
  213. void OnWriteCompleted(MojoResult result) {
  214. network::URLLoaderCompletionStatus status(
  215. result == MOJO_RESULT_OK ? net::OK : net::ERR_INVALID_WEB_BUNDLE);
  216. status.encoded_data_length = body_length_ + headers_bytes_;
  217. // For these values we use the same `body_length_` as we don't currently
  218. // provide encoding in WebBundles.
  219. status.encoded_body_length = body_length_;
  220. status.decoded_body_length = body_length_;
  221. client_->OnComplete(status);
  222. delete this;
  223. }
  224. void BlockResponseForCorb(network::mojom::URLResponseHeadPtr response_head) {
  225. // A minimum implementation to block CORB-protected resources.
  226. //
  227. // TODO(crbug.com/1082020): Re-use
  228. // network::URLLoader::BlockResponseForCorb(), instead of copying
  229. // essential parts from there, so that the two implementations won't
  230. // diverge further. That requires non-trivial refactoring.
  231. network::corb::SanitizeBlockedResponseHeaders(*response_head);
  232. // Send empty body to the URLLoaderClient.
  233. mojo::ScopedDataPipeProducerHandle producer;
  234. mojo::ScopedDataPipeConsumerHandle consumer;
  235. if (CreateDataPipe(nullptr, producer, consumer) != MOJO_RESULT_OK) {
  236. OnFail(net::ERR_INSUFFICIENT_RESOURCES);
  237. return;
  238. }
  239. producer.reset();
  240. client_->OnReceiveResponse(std::move(response_head), std::move(consumer));
  241. // CORB responses are reported as a success.
  242. CompleteBlockedResponse(net::OK, absl::nullopt);
  243. }
  244. bool is_trusted() const { return is_trusted_; }
  245. void CompleteBlockedResponse(
  246. int error_code,
  247. absl::optional<network::mojom::BlockedByResponseReason> reason) {
  248. network::URLLoaderCompletionStatus status;
  249. status.error_code = error_code;
  250. status.completion_time = base::TimeTicks::Now();
  251. status.encoded_data_length = 0;
  252. status.encoded_body_length = 0;
  253. status.decoded_body_length = 0;
  254. status.blocked_by_response_reason = reason;
  255. client_->OnComplete(status);
  256. // Reset the connection to the URLLoaderClient. This helps ensure that we
  257. // won't accidentally leak any data to the renderer from this point on.
  258. client_.reset();
  259. delete this;
  260. }
  261. mojo::Remote<network::mojom::TrustedHeaderClient>& trusted_header_client() {
  262. return trusted_header_client_;
  263. }
  264. net::LoadTimingInfo load_timing() { return load_timing_; }
  265. void SetBodyLength(uint64_t body_length) { body_length_ = body_length; }
  266. void SetHeadersBytes(size_t headers_bytes) { headers_bytes_ = headers_bytes; }
  267. void SetResponseStartTime(base::TimeTicks response_start_time) {
  268. load_timing_.receive_headers_start = response_start_time;
  269. load_timing_.receive_headers_end = response_start_time;
  270. }
  271. private:
  272. // network::mojom::URLLoader
  273. void FollowRedirect(
  274. const std::vector<std::string>& removed_headers,
  275. const net::HttpRequestHeaders& modified_headers,
  276. const net::HttpRequestHeaders& modified_cors_exempt_headers,
  277. const absl::optional<GURL>& new_url) override {
  278. NOTREACHED();
  279. }
  280. void SetPriority(net::RequestPriority priority,
  281. int32_t intra_priority_value) override {
  282. // Not supported (do nothing).
  283. }
  284. void PauseReadingBodyFromNet() override {}
  285. void ResumeReadingBodyFromNet() override {}
  286. void OnMojoDisconnect() { delete this; }
  287. const GURL url_;
  288. network::mojom::RequestMode request_mode_;
  289. absl::optional<url::Origin> request_initiator_;
  290. network::mojom::RequestDestination request_destination_;
  291. absl::optional<std::string> devtools_request_id_;
  292. const bool is_trusted_;
  293. mojo::Receiver<network::mojom::URLLoader> receiver_;
  294. mojo::Remote<network::mojom::URLLoaderClient> client_;
  295. mojo::Remote<network::mojom::TrustedHeaderClient> trusted_header_client_;
  296. uint64_t body_length_;
  297. size_t headers_bytes_;
  298. net::LoadTimingInfo load_timing_;
  299. base::TimeTicks request_send_time_;
  300. base::TimeTicks response_start_time_;
  301. base::WeakPtrFactory<URLLoader> weak_ptr_factory_{this};
  302. };
  303. class WebBundleURLLoaderFactory::BundleDataSource
  304. : public web_package::mojom::BundleDataSource,
  305. public mojo::DataPipeDrainer::Client {
  306. public:
  307. using ReadToDataPipeCallback = base::OnceCallback<void(MojoResult result)>;
  308. BundleDataSource(mojo::PendingReceiver<web_package::mojom::BundleDataSource>
  309. data_source_receiver,
  310. mojo::ScopedDataPipeConsumerHandle bundle_body,
  311. std::unique_ptr<WebBundleMemoryQuotaConsumer>
  312. web_bundle_memory_quota_consumer,
  313. base::OnceClosure memory_quota_exceeded_closure,
  314. base::OnceClosure data_completed_closure)
  315. : data_source_receiver_(this, std::move(data_source_receiver)),
  316. pipe_drainer_(
  317. std::make_unique<mojo::DataPipeDrainer>(this,
  318. std::move(bundle_body))),
  319. web_bundle_memory_quota_consumer_(
  320. std::move(web_bundle_memory_quota_consumer)),
  321. memory_quota_exceeded_closure_(
  322. std::move(memory_quota_exceeded_closure)),
  323. data_completed_closure_(std::move(data_completed_closure)) {}
  324. ~BundleDataSource() override {
  325. // The receiver must be closed before destructing pending callbacks in
  326. // |pending_reads_| / |pending_reads_to_data_pipe_|.
  327. data_source_receiver_.reset();
  328. }
  329. BundleDataSource(const BundleDataSource&) = delete;
  330. BundleDataSource& operator=(const BundleDataSource&) = delete;
  331. void ReadToDataPipe(mojo::ScopedDataPipeProducerHandle producer,
  332. uint64_t offset,
  333. uint64_t length,
  334. ReadToDataPipeCallback callback) {
  335. TRACE_EVENT0("loading", "BundleDataSource::ReadToDataPipe");
  336. if (!finished_loading_ && !buffer_.ContainsAll(offset, length)) {
  337. // Current implementation does not support progressive loading of inner
  338. // response body.
  339. PendingReadToDataPipe pending;
  340. pending.producer = std::move(producer);
  341. pending.offset = offset;
  342. pending.length = length;
  343. pending.callback = std::move(callback);
  344. pending_reads_to_data_pipe_.push_back(std::move(pending));
  345. return;
  346. }
  347. auto data_source = buffer_.CreateDataSource(offset, length);
  348. if (!data_source) {
  349. // When there is no body to send, returns OK here without creating a
  350. // DataPipeProducer.
  351. std::move(callback).Run(MOJO_RESULT_OK);
  352. return;
  353. }
  354. auto writer = std::make_unique<mojo::DataPipeProducer>(std::move(producer));
  355. mojo::DataPipeProducer* raw_writer = writer.get();
  356. raw_writer->Write(std::move(data_source),
  357. base::BindOnce(&DeleteProducerAndRunCallback,
  358. std::move(writer), std::move(callback)));
  359. }
  360. // Implements mojom::BundleDataSource.
  361. void Read(uint64_t offset, uint64_t length, ReadCallback callback) override {
  362. TRACE_EVENT0("loading", "BundleDataSource::Read");
  363. if (!finished_loading_ && !buffer_.ContainsAll(offset, length)) {
  364. PendingRead pending;
  365. pending.offset = offset;
  366. pending.length = length;
  367. pending.callback = std::move(callback);
  368. pending_reads_.push_back(std::move(pending));
  369. return;
  370. }
  371. uint64_t out_len = buffer_.GetAvailableLength(offset, length);
  372. std::vector<uint8_t> output(base::checked_cast<size_t>(out_len));
  373. buffer_.ReadData(offset, out_len, output.data());
  374. std::move(callback).Run(std::move(output));
  375. }
  376. void Length(LengthCallback callback) override { std::move(callback).Run(-1); }
  377. void IsRandomAccessContext(IsRandomAccessContextCallback callback) override {
  378. std::move(callback).Run(false);
  379. }
  380. // Implements mojo::DataPipeDrainer::Client.
  381. void OnDataAvailable(const void* data, size_t num_bytes) override {
  382. DCHECK(!finished_loading_);
  383. if (!web_bundle_memory_quota_consumer_->AllocateMemory(num_bytes)) {
  384. AbortPendingReads();
  385. if (memory_quota_exceeded_closure_) {
  386. // Defer calling |memory_quota_exceeded_closure_| to avoid the
  387. // UAF call in DataPipeDrainer::ReadData().
  388. base::SequencedTaskRunnerHandle::Get()->PostTask(
  389. FROM_HERE, std::move(memory_quota_exceeded_closure_));
  390. }
  391. return;
  392. }
  393. buffer_.Append(reinterpret_cast<const uint8_t*>(data), num_bytes);
  394. ProcessPendingReads();
  395. }
  396. void OnDataComplete() override {
  397. DCHECK(!finished_loading_);
  398. base::UmaHistogramCustomCounts(
  399. "SubresourceWebBundles.ReceivedSize",
  400. base::saturated_cast<base::Histogram::Sample>(buffer_.size()), 1,
  401. 50000000, 50);
  402. DCHECK(data_completed_closure_);
  403. // Defer calling |data_completed_closure_| not to run
  404. // |data_completed_closure_| before |memory_quota_exceeded_closure_|.
  405. base::SequencedTaskRunnerHandle::Get()->PostTask(
  406. FROM_HERE, std::move(data_completed_closure_));
  407. finished_loading_ = true;
  408. ProcessPendingReads();
  409. }
  410. private:
  411. void ProcessPendingReads() {
  412. std::vector<PendingRead> pendings(std::move(pending_reads_));
  413. std::vector<PendingReadToDataPipe> pipe_pendings(
  414. std::move(pending_reads_to_data_pipe_));
  415. for (auto& pending : pendings) {
  416. Read(pending.offset, pending.length, std::move(pending.callback));
  417. }
  418. for (auto& pending : pipe_pendings) {
  419. ReadToDataPipe(std::move(pending.producer), pending.offset,
  420. pending.length, std::move(pending.callback));
  421. }
  422. }
  423. void AbortPendingReads() {
  424. std::vector<PendingRead> pendings(std::move(pending_reads_));
  425. std::vector<PendingReadToDataPipe> pipe_pendings(
  426. std::move(pending_reads_to_data_pipe_));
  427. for (auto& pending : pendings) {
  428. std::move(pending.callback).Run(std::vector<uint8_t>());
  429. }
  430. for (auto& pending : pipe_pendings) {
  431. std::move(pending.callback).Run(MOJO_RESULT_NOT_FOUND);
  432. }
  433. }
  434. struct PendingRead {
  435. uint64_t offset;
  436. uint64_t length;
  437. ReadCallback callback;
  438. };
  439. struct PendingReadToDataPipe {
  440. mojo::ScopedDataPipeProducerHandle producer;
  441. uint64_t offset;
  442. uint64_t length;
  443. ReadToDataPipeCallback callback;
  444. };
  445. mojo::Receiver<web_package::mojom::BundleDataSource> data_source_receiver_;
  446. WebBundleChunkedBuffer buffer_;
  447. std::vector<PendingRead> pending_reads_;
  448. std::vector<PendingReadToDataPipe> pending_reads_to_data_pipe_;
  449. bool finished_loading_ = false;
  450. std::unique_ptr<mojo::DataPipeDrainer> pipe_drainer_;
  451. std::unique_ptr<WebBundleMemoryQuotaConsumer>
  452. web_bundle_memory_quota_consumer_;
  453. base::OnceClosure memory_quota_exceeded_closure_;
  454. base::OnceClosure data_completed_closure_;
  455. };
  456. WebBundleURLLoaderFactory::WebBundleURLLoaderFactory(
  457. const GURL& bundle_url,
  458. const network::ResourceRequest::WebBundleTokenParams&
  459. web_bundle_token_params,
  460. mojo::Remote<network::mojom::WebBundleHandle> web_bundle_handle,
  461. std::unique_ptr<WebBundleMemoryQuotaConsumer>
  462. web_bundle_memory_quota_consumer,
  463. mojo::PendingRemote<network::mojom::DevToolsObserver> devtools_observer,
  464. absl::optional<std::string> devtools_request_id,
  465. const network::CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
  466. network::mojom::CrossOriginEmbedderPolicyReporter* coep_reporter)
  467. : bundle_url_(bundle_url),
  468. web_bundle_handle_(std::move(web_bundle_handle)),
  469. web_bundle_memory_quota_consumer_(
  470. std::move(web_bundle_memory_quota_consumer)),
  471. devtools_observer_(std::move(devtools_observer)),
  472. devtools_request_id_(std::move(devtools_request_id)),
  473. cross_origin_embedder_policy_(cross_origin_embedder_policy),
  474. coep_reporter_(coep_reporter) {
  475. if (bundle_url != web_bundle_token_params.bundle_url) {
  476. // This happens when WebBundle request is redirected by WebRequest extension
  477. // API.
  478. // TODO(crbug.com/1242281): Support redirection for WebBundle requests.
  479. ReportErrorAndCancelPendingLoaders(
  480. SubresourceWebBundleLoadResult::kWebBundleRedirected,
  481. network::mojom::WebBundleErrorType::kWebBundleRedirected,
  482. "URL redirection of Subresource Web Bundles is currently not "
  483. "supported.");
  484. }
  485. }
  486. WebBundleURLLoaderFactory::~WebBundleURLLoaderFactory() {
  487. for (auto loader : pending_loaders_) {
  488. if (loader)
  489. loader->OnFail(net::ERR_FAILED);
  490. }
  491. }
  492. base::WeakPtr<WebBundleURLLoaderFactory> WebBundleURLLoaderFactory::GetWeakPtr()
  493. const {
  494. return weak_ptr_factory_.GetWeakPtr();
  495. }
  496. bool WebBundleURLLoaderFactory::HasError() const {
  497. return load_result_.has_value() &&
  498. *load_result_ != SubresourceWebBundleLoadResult::kSuccess;
  499. }
  500. void WebBundleURLLoaderFactory::SetBundleStream(
  501. mojo::ScopedDataPipeConsumerHandle body) {
  502. if (HasError())
  503. return;
  504. mojo::PendingRemote<web_package::mojom::BundleDataSource> data_source;
  505. source_ = std::make_unique<BundleDataSource>(
  506. data_source.InitWithNewPipeAndPassReceiver(), std::move(body),
  507. std::move(web_bundle_memory_quota_consumer_),
  508. base::BindOnce(&WebBundleURLLoaderFactory::OnMemoryQuotaExceeded,
  509. weak_ptr_factory_.GetWeakPtr()),
  510. base::BindOnce(&WebBundleURLLoaderFactory::OnDataCompleted,
  511. weak_ptr_factory_.GetWeakPtr()));
  512. // WebBundleParser will self-destruct on remote mojo ends' disconnection.
  513. new web_package::WebBundleParser(parser_.BindNewPipeAndPassReceiver(),
  514. std::move(data_source), bundle_url_);
  515. parser_->ParseMetadata(
  516. /*offset=*/-1,
  517. base::BindOnce(&WebBundleURLLoaderFactory::OnMetadataParsed,
  518. weak_ptr_factory_.GetWeakPtr()));
  519. }
  520. mojo::PendingRemote<network::mojom::URLLoaderClient>
  521. WebBundleURLLoaderFactory::MaybeWrapURLLoaderClient(
  522. mojo::PendingRemote<network::mojom::URLLoaderClient> wrapped) {
  523. if (HasError()) {
  524. mojo::Remote<network::mojom::URLLoaderClient>(std::move(wrapped))
  525. ->OnComplete(
  526. network::URLLoaderCompletionStatus(net::ERR_INVALID_WEB_BUNDLE));
  527. return {};
  528. }
  529. mojo::PendingRemote<network::mojom::URLLoaderClient> client;
  530. auto client_impl = std::make_unique<WebBundleURLLoaderClient>(
  531. weak_ptr_factory_.GetWeakPtr(), std::move(wrapped));
  532. mojo::MakeSelfOwnedReceiver(std::move(client_impl),
  533. client.InitWithNewPipeAndPassReceiver());
  534. return client;
  535. }
  536. void WebBundleURLLoaderFactory::StartSubresourceRequest(
  537. mojo::PendingReceiver<network::mojom::URLLoader> receiver,
  538. const network::ResourceRequest& url_request,
  539. mojo::PendingRemote<network::mojom::URLLoaderClient> client,
  540. mojo::Remote<network::mojom::TrustedHeaderClient> trusted_header_client,
  541. base::Time request_start_time,
  542. base::TimeTicks request_start_time_ticks) {
  543. TRACE_EVENT0("loading", "WebBundleURLLoaderFactory::StartSubresourceRequest");
  544. URLLoader* loader =
  545. new URLLoader(std::move(receiver), url_request, std::move(client),
  546. std::move(trusted_header_client), request_start_time,
  547. request_start_time_ticks);
  548. if (HasError()) {
  549. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  550. return;
  551. }
  552. // Verify that WebBundle URL associated with the request is correct.
  553. DCHECK(url_request.web_bundle_token_params.has_value());
  554. if (url_request.web_bundle_token_params->bundle_url != bundle_url_) {
  555. mojo::ReportBadMessage(
  556. "WebBundleURLLoaderFactory: Bundle URL does not match");
  557. loader->OnFail(net::ERR_INVALID_ARGUMENT);
  558. return;
  559. }
  560. if (!loader->trusted_header_client()) {
  561. QueueOrStartLoader(loader->GetWeakPtr());
  562. return;
  563. }
  564. loader->trusted_header_client()->OnBeforeSendHeaders(
  565. url_request.headers,
  566. base::BindOnce(&WebBundleURLLoaderFactory::OnBeforeSendHeadersComplete,
  567. weak_ptr_factory_.GetWeakPtr(), loader->GetWeakPtr()));
  568. }
  569. void WebBundleURLLoaderFactory::OnBeforeSendHeadersComplete(
  570. base::WeakPtr<URLLoader> loader,
  571. int result,
  572. const absl::optional<net::HttpRequestHeaders>& headers) {
  573. if (!loader)
  574. return;
  575. QueueOrStartLoader(loader);
  576. }
  577. void WebBundleURLLoaderFactory::QueueOrStartLoader(
  578. base::WeakPtr<URLLoader> loader) {
  579. if (!loader)
  580. return;
  581. if (HasError()) {
  582. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  583. return;
  584. }
  585. if (!metadata_) {
  586. pending_loaders_.push_back(loader);
  587. return;
  588. }
  589. StartLoad(loader);
  590. }
  591. void WebBundleURLLoaderFactory::StartLoad(base::WeakPtr<URLLoader> loader) {
  592. DCHECK(metadata_);
  593. if (!loader)
  594. return;
  595. auto it = metadata_->requests.find(loader->url());
  596. if (it == metadata_->requests.end()) {
  597. web_bundle_handle_->OnWebBundleError(
  598. network::mojom::WebBundleErrorType::kResourceNotFound,
  599. loader->url().possibly_invalid_spec() +
  600. " is not found in the WebBundle.");
  601. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  602. return;
  603. }
  604. parser_->ParseResponse(
  605. it->second->offset, it->second->length,
  606. base::BindOnce(&WebBundleURLLoaderFactory::OnResponseParsed,
  607. weak_ptr_factory_.GetWeakPtr(), loader->GetWeakPtr()));
  608. }
  609. void WebBundleURLLoaderFactory::ReportErrorAndCancelPendingLoaders(
  610. SubresourceWebBundleLoadResult result,
  611. network::mojom::WebBundleErrorType error,
  612. const std::string& message) {
  613. DCHECK_NE(SubresourceWebBundleLoadResult::kSuccess, result);
  614. web_bundle_handle_->OnWebBundleError(error, message);
  615. MaybeReportLoadResult(result);
  616. auto pending_loaders = std::move(pending_loaders_);
  617. for (auto loader : pending_loaders) {
  618. if (loader)
  619. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  620. }
  621. source_.reset();
  622. parser_.reset();
  623. }
  624. void WebBundleURLLoaderFactory::OnMetadataParsed(
  625. web_package::mojom::BundleMetadataPtr metadata,
  626. web_package::mojom::BundleMetadataParseErrorPtr error) {
  627. TRACE_EVENT0("loading", "WebBundleURLLoaderFactory::OnMetadataParsed");
  628. if (error) {
  629. ReportErrorAndCancelPendingLoaders(
  630. SubresourceWebBundleLoadResult::kMetadataParseError,
  631. network::mojom::WebBundleErrorType::kMetadataParseError,
  632. error->message);
  633. if (devtools_request_id_) {
  634. devtools_observer_->OnSubresourceWebBundleMetadataError(
  635. *devtools_request_id_, error->message);
  636. }
  637. return;
  638. }
  639. if (!base::ranges::all_of(metadata->requests, [this](const auto& entry) {
  640. return IsAllowedExchangeUrl(entry.first);
  641. })) {
  642. std::string error_message = "Exchange URL is not valid.";
  643. ReportErrorAndCancelPendingLoaders(
  644. SubresourceWebBundleLoadResult::kMetadataParseError,
  645. network::mojom::WebBundleErrorType::kMetadataParseError, error_message);
  646. if (devtools_request_id_) {
  647. devtools_observer_->OnSubresourceWebBundleMetadataError(
  648. *devtools_request_id_, error_message);
  649. }
  650. return;
  651. }
  652. metadata_ = std::move(metadata);
  653. if (devtools_observer_ && devtools_request_id_) {
  654. std::vector<GURL> urls;
  655. urls.reserve(metadata_->requests.size());
  656. for (const auto& item : metadata_->requests) {
  657. urls.push_back(item.first);
  658. }
  659. devtools_observer_->OnSubresourceWebBundleMetadata(*devtools_request_id_,
  660. std::move(urls));
  661. }
  662. if (metadata_->version == web_package::mojom::BundleFormatVersion::kB1) {
  663. web_bundle_handle_->OnWebBundleError(
  664. network::mojom::WebBundleErrorType::kDeprecationWarning,
  665. "WebBundle format \"b1\" is deprecated. See migration guide at "
  666. "https://bit.ly/3rpDuEX.");
  667. }
  668. if (data_completed_)
  669. MaybeReportLoadResult(SubresourceWebBundleLoadResult::kSuccess);
  670. for (auto loader : pending_loaders_)
  671. StartLoad(loader);
  672. pending_loaders_.clear();
  673. }
  674. bool WebBundleURLLoaderFactory::IsAllowedExchangeUrl(const GURL& relative_url) {
  675. GURL url = bundle_url_.Resolve(relative_url.spec());
  676. return url.SchemeIsHTTPOrHTTPS() || web_package::IsValidUuidInPackageURL(url);
  677. }
  678. void WebBundleURLLoaderFactory::OnResponseParsed(
  679. base::WeakPtr<URLLoader> loader,
  680. web_package::mojom::BundleResponsePtr response,
  681. web_package::mojom::BundleResponseParseErrorPtr error) {
  682. TRACE_EVENT0("loading", "WebBundleURLLoaderFactory::OnResponseParsed");
  683. if (!loader)
  684. return;
  685. if (error) {
  686. if (devtools_observer_ && loader->devtools_request_id()) {
  687. devtools_observer_->OnSubresourceWebBundleInnerResponseError(
  688. *loader->devtools_request_id(), loader->url(), error->message,
  689. devtools_request_id_);
  690. }
  691. web_bundle_handle_->OnWebBundleError(
  692. network::mojom::WebBundleErrorType::kResponseParseError,
  693. error->message);
  694. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  695. return;
  696. }
  697. if (devtools_observer_) {
  698. std::vector<network::mojom::HttpRawHeaderPairPtr> headers;
  699. headers.reserve(response->response_headers.size());
  700. for (const auto& it : response->response_headers) {
  701. headers.push_back(
  702. network::mojom::HttpRawHeaderPair::New(it.first, it.second));
  703. }
  704. if (loader->devtools_request_id()) {
  705. devtools_observer_->OnSubresourceWebBundleInnerResponse(
  706. *loader->devtools_request_id(), loader->url(), devtools_request_id_);
  707. }
  708. }
  709. // Add an artificial "X-Content-Type-Options: "nosniff" header, which is
  710. // explained at
  711. // https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-responses.
  712. response->response_headers["X-Content-Type-Options"] = "nosniff";
  713. const std::string header_string = web_package::CreateHeaderString(response);
  714. loader->SetResponseStartTime(base::TimeTicks::Now());
  715. loader->SetHeadersBytes(header_string.size());
  716. if (!loader->trusted_header_client()) {
  717. SendResponseToLoader(loader, header_string, response->payload_offset,
  718. response->payload_length);
  719. return;
  720. }
  721. loader->trusted_header_client()->OnHeadersReceived(
  722. header_string, net::IPEndPoint(),
  723. base::BindOnce(&WebBundleURLLoaderFactory::OnHeadersReceivedComplete,
  724. weak_ptr_factory_.GetWeakPtr(), loader->GetWeakPtr(),
  725. header_string, response->payload_offset,
  726. response->payload_length));
  727. }
  728. void WebBundleURLLoaderFactory::OnHeadersReceivedComplete(
  729. base::WeakPtr<URLLoader> loader,
  730. const std::string& original_header,
  731. uint64_t payload_offset,
  732. uint64_t payload_length,
  733. int result,
  734. const absl::optional<std::string>& headers,
  735. const absl::optional<GURL>& preserve_fragment_on_redirect_url) {
  736. if (!loader)
  737. return;
  738. SendResponseToLoader(loader, headers ? *headers : original_header,
  739. payload_offset, payload_length);
  740. }
  741. void WebBundleURLLoaderFactory::SendResponseToLoader(
  742. base::WeakPtr<URLLoader> loader,
  743. const std::string& headers,
  744. uint64_t payload_offset,
  745. uint64_t payload_length) {
  746. if (!loader)
  747. return;
  748. network::mojom::URLResponseHeadPtr response_head =
  749. web_package::CreateResourceResponseFromHeaderString(headers);
  750. // Currently we allow only net::HTTP_OK responses in bundles.
  751. // TODO(crbug.com/990733): Revisit this once
  752. // https://github.com/WICG/webpackage/issues/478 is resolved.
  753. if (response_head->headers->response_code() != net::HTTP_OK) {
  754. web_bundle_handle_->OnWebBundleError(
  755. network::mojom::WebBundleErrorType::kResponseParseError,
  756. "Invalid response code " +
  757. base::NumberToString(response_head->headers->response_code()));
  758. loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
  759. return;
  760. }
  761. response_head->web_bundle_url = bundle_url_;
  762. response_head->load_timing = loader->load_timing();
  763. loader->SetBodyLength(payload_length);
  764. // Enforce the Cross-Origin-Resource-Policy (CORP) header.
  765. if (absl::optional<network::mojom::BlockedByResponseReason> blocked_reason =
  766. network::CrossOriginResourcePolicy::IsBlocked(
  767. loader->url(), loader->url(), loader->request_initiator(),
  768. *response_head, loader->request_mode(),
  769. loader->request_destination(), cross_origin_embedder_policy_,
  770. coep_reporter_)) {
  771. loader->CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE,
  772. blocked_reason);
  773. return;
  774. }
  775. // Enforce FLEDGE auction-only signals -- the renderer process isn't allowed
  776. // to read auction-only signals for FLEDGE auctions; only the browser process
  777. // is allowed to read those, and only the browser process can issue trusted
  778. // requests.
  779. std::string fledge_auction_only_signals;
  780. if (!loader->is_trusted() && response_head->headers &&
  781. response_head->headers->GetNormalizedHeader(
  782. "X-FLEDGE-Auction-Only", &fledge_auction_only_signals) &&
  783. base::EqualsCaseInsensitiveASCII(fledge_auction_only_signals, "true")) {
  784. loader->CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE,
  785. /*reason=*/absl::nullopt);
  786. return;
  787. }
  788. auto corb_analyzer = network::corb::ResponseAnalyzer::Create(corb_state_);
  789. auto decision =
  790. corb_analyzer->Init(loader->url(), loader->request_initiator(),
  791. loader->request_mode(), *response_head);
  792. switch (decision) {
  793. case network::corb::ResponseAnalyzer::Decision::kBlock:
  794. loader->BlockResponseForCorb(std::move(response_head));
  795. return;
  796. case network::corb::ResponseAnalyzer::Decision::kAllow:
  797. case network::corb::ResponseAnalyzer::Decision::kSniffMore:
  798. break;
  799. }
  800. mojo::ScopedDataPipeProducerHandle producer;
  801. mojo::ScopedDataPipeConsumerHandle consumer;
  802. if (CreateDataPipe(nullptr, producer, consumer) != MOJO_RESULT_OK) {
  803. loader->OnFail(net::ERR_INSUFFICIENT_RESOURCES);
  804. return;
  805. }
  806. loader->OnResponse(std::move(response_head), std::move(consumer));
  807. source_->ReadToDataPipe(
  808. std::move(producer), payload_offset, payload_length,
  809. base::BindOnce(&URLLoader::OnWriteCompleted, loader->GetWeakPtr()));
  810. }
  811. void WebBundleURLLoaderFactory::OnMemoryQuotaExceeded() {
  812. TRACE_EVENT0("loading", "WebBundleURLLoaderFactory::OnMemoryQuotaExceeded");
  813. ReportErrorAndCancelPendingLoaders(
  814. SubresourceWebBundleLoadResult::kMemoryQuotaExceeded,
  815. network::mojom::WebBundleErrorType::kMemoryQuotaExceeded,
  816. "Memory quota exceeded. Currently, there is an upper limit on the total "
  817. "size of subresource web bundles in a process. See "
  818. "https://crbug.com/1154140 for more details.");
  819. }
  820. void WebBundleURLLoaderFactory::OnDataCompleted() {
  821. DCHECK(!data_completed_);
  822. data_completed_ = true;
  823. if (metadata_)
  824. MaybeReportLoadResult(SubresourceWebBundleLoadResult::kSuccess);
  825. }
  826. void WebBundleURLLoaderFactory::MaybeReportLoadResult(
  827. SubresourceWebBundleLoadResult result) {
  828. if (load_result_.has_value())
  829. return;
  830. load_result_ = result;
  831. base::UmaHistogramEnumeration("SubresourceWebBundles.LoadResult", result);
  832. web_bundle_handle_->OnWebBundleLoadFinished(
  833. result == SubresourceWebBundleLoadResult::kSuccess);
  834. }
  835. void WebBundleURLLoaderFactory::OnWebBundleFetchFailed() {
  836. ReportErrorAndCancelPendingLoaders(
  837. SubresourceWebBundleLoadResult::kWebBundleFetchFailed,
  838. network::mojom::WebBundleErrorType::kWebBundleFetchFailed,
  839. "Failed to fetch the Web Bundle.");
  840. }
  841. } // namespace web_package