electron_api_url_loader.cc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. // Copyright (c) 2019 Slack Technologies, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/common/api/electron_api_url_loader.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <string>
  8. #include <string_view>
  9. #include <utility>
  10. #include <vector>
  11. #include "base/check_op.h"
  12. #include "base/containers/fixed_flat_map.h"
  13. #include "base/memory/raw_ptr.h"
  14. #include "base/notreached.h"
  15. #include "base/sequence_checker.h"
  16. #include "gin/handle.h"
  17. #include "gin/object_template_builder.h"
  18. #include "gin/wrappable.h"
  19. #include "mojo/public/cpp/bindings/remote.h"
  20. #include "mojo/public/cpp/system/data_pipe_producer.h"
  21. #include "net/base/auth.h"
  22. #include "net/base/load_flags.h"
  23. #include "net/http/http_util.h"
  24. #include "net/url_request/redirect_util.h"
  25. #include "services/network/public/cpp/resource_request.h"
  26. #include "services/network/public/cpp/simple_url_loader.h"
  27. #include "services/network/public/cpp/url_util.h"
  28. #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
  29. #include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
  30. #include "services/network/public/mojom/http_raw_headers.mojom.h"
  31. #include "services/network/public/mojom/shared_storage.mojom.h"
  32. #include "services/network/public/mojom/url_loader_factory.mojom.h"
  33. #include "shell/browser/api/electron_api_session.h"
  34. #include "shell/browser/electron_browser_context.h"
  35. #include "shell/browser/javascript_environment.h"
  36. #include "shell/browser/net/asar/asar_url_loader_factory.h"
  37. #include "shell/browser/net/proxying_url_loader_factory.h"
  38. #include "shell/browser/protocol_registry.h"
  39. #include "shell/common/gin_converters/callback_converter.h"
  40. #include "shell/common/gin_converters/gurl_converter.h"
  41. #include "shell/common/gin_converters/net_converter.h"
  42. #include "shell/common/gin_helper/dictionary.h"
  43. #include "shell/common/gin_helper/object_template_builder.h"
  44. #include "shell/common/gin_helper/promise.h"
  45. #include "shell/common/node_includes.h"
  46. #include "shell/common/process_util.h"
  47. #include "shell/services/node/node_service.h"
  48. #include "third_party/blink/public/common/loader/referrer_utils.h"
  49. #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
  50. namespace gin {
  51. template <>
  52. struct Converter<network::mojom::HttpRawHeaderPairPtr> {
  53. static v8::Local<v8::Value> ToV8(
  54. v8::Isolate* isolate,
  55. const network::mojom::HttpRawHeaderPairPtr& pair) {
  56. auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
  57. dict.Set("key", pair->key);
  58. dict.Set("value", pair->value);
  59. return dict.GetHandle();
  60. }
  61. };
  62. template <>
  63. struct Converter<network::mojom::CredentialsMode> {
  64. static bool FromV8(v8::Isolate* isolate,
  65. v8::Local<v8::Value> val,
  66. network::mojom::CredentialsMode* out) {
  67. using Val = network::mojom::CredentialsMode;
  68. static constexpr auto Lookup =
  69. base::MakeFixedFlatMap<std::string_view, Val>({
  70. {"include", Val::kInclude},
  71. {"omit", Val::kOmit},
  72. // Note: This only makes sense if the request
  73. // specifies the "origin" option.
  74. {"same-origin", Val::kSameOrigin},
  75. });
  76. return FromV8WithLookup(isolate, val, Lookup, out);
  77. }
  78. };
  79. template <>
  80. struct Converter<blink::mojom::FetchCacheMode> {
  81. static bool FromV8(v8::Isolate* isolate,
  82. v8::Local<v8::Value> val,
  83. blink::mojom::FetchCacheMode* out) {
  84. using Val = blink::mojom::FetchCacheMode;
  85. static constexpr auto Lookup =
  86. base::MakeFixedFlatMap<std::string_view, Val>({
  87. {"default", Val::kDefault},
  88. {"force-cache", Val::kForceCache},
  89. {"no-cache", Val::kValidateCache},
  90. {"no-store", Val::kNoStore},
  91. {"only-if-cached", Val::kOnlyIfCached},
  92. {"reload", Val::kBypassCache},
  93. });
  94. return FromV8WithLookup(isolate, val, Lookup, out);
  95. }
  96. };
  97. template <>
  98. struct Converter<net::ReferrerPolicy> {
  99. static bool FromV8(v8::Isolate* isolate,
  100. v8::Local<v8::Value> val,
  101. net::ReferrerPolicy* out) {
  102. using Val = net::ReferrerPolicy;
  103. // clang-format off
  104. static constexpr auto Lookup =
  105. base::MakeFixedFlatMap<std::string_view, Val>({
  106. {"", Val::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN},
  107. {"no-referrer", Val::NO_REFERRER},
  108. {"no-referrer-when-downgrade", Val::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE},
  109. {"origin", Val::ORIGIN},
  110. {"origin-when-cross-origin", Val::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN},
  111. {"same-origin", Val::CLEAR_ON_TRANSITION_CROSS_ORIGIN},
  112. {"strict-origin", Val:: ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE},
  113. {"strict-origin-when-cross-origin", Val::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN},
  114. {"unsafe-url", Val::NEVER_CLEAR},
  115. });
  116. // clang-format on
  117. return FromV8WithLowerLookup(isolate, val, Lookup, out);
  118. }
  119. };
  120. } // namespace gin
  121. namespace electron::api {
  122. namespace {
  123. template <typename T>
  124. auto ToVec(v8::Local<v8::ArrayBufferView> view) {
  125. const size_t n_wanted = view->ByteLength();
  126. std::vector<T> buf(n_wanted);
  127. [[maybe_unused]] const auto n_got = view->CopyContents(buf.data(), n_wanted);
  128. DCHECK_EQ(n_wanted, n_got);
  129. DCHECK_EQ(n_wanted, std::size(buf));
  130. return buf;
  131. }
  132. class BufferDataSource : public mojo::DataPipeProducer::DataSource {
  133. public:
  134. explicit BufferDataSource(v8::Local<v8::ArrayBufferView> buffer)
  135. : buffer_{ToVec<char>(buffer)} {}
  136. ~BufferDataSource() override = default;
  137. private:
  138. // mojo::DataPipeProducer::DataSource:
  139. [[nodiscard]] uint64_t GetLength() const override { return buffer_.size(); }
  140. ReadResult Read(uint64_t offset, base::span<char> buffer) override {
  141. ReadResult result;
  142. if (offset <= buffer_.size()) {
  143. size_t readable_size = buffer_.size() - offset;
  144. size_t writable_size = buffer.size();
  145. size_t copyable_size = std::min(readable_size, writable_size);
  146. if (copyable_size > 0) {
  147. memcpy(buffer.data(), &buffer_[offset], copyable_size);
  148. }
  149. result.bytes_read = copyable_size;
  150. } else {
  151. NOTREACHED();
  152. }
  153. return result;
  154. }
  155. std::vector<char> buffer_;
  156. };
  157. class JSChunkedDataPipeGetter final
  158. : public gin::Wrappable<JSChunkedDataPipeGetter>,
  159. public network::mojom::ChunkedDataPipeGetter {
  160. public:
  161. static gin::Handle<JSChunkedDataPipeGetter> Create(
  162. v8::Isolate* isolate,
  163. v8::Local<v8::Function> body_func,
  164. mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
  165. chunked_data_pipe_getter) {
  166. return gin::CreateHandle(
  167. isolate, new JSChunkedDataPipeGetter(
  168. isolate, body_func, std::move(chunked_data_pipe_getter)));
  169. }
  170. // gin::Wrappable
  171. gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
  172. v8::Isolate* isolate) override {
  173. return gin::Wrappable<JSChunkedDataPipeGetter>::GetObjectTemplateBuilder(
  174. isolate)
  175. .SetMethod("write", &JSChunkedDataPipeGetter::WriteChunk)
  176. .SetMethod("done", &JSChunkedDataPipeGetter::Done);
  177. }
  178. const char* GetTypeName() override { return "JSChunkedDataPipeGetter"; }
  179. static gin::WrapperInfo kWrapperInfo;
  180. ~JSChunkedDataPipeGetter() override = default;
  181. private:
  182. JSChunkedDataPipeGetter(
  183. v8::Isolate* isolate,
  184. v8::Local<v8::Function> body_func,
  185. mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
  186. chunked_data_pipe_getter)
  187. : isolate_(isolate), body_func_(isolate, body_func) {
  188. DETACH_FROM_SEQUENCE(sequence_checker_);
  189. receiver_.Bind(std::move(chunked_data_pipe_getter));
  190. }
  191. // network::mojom::ChunkedDataPipeGetter:
  192. void GetSize(GetSizeCallback callback) override {
  193. size_callback_ = std::move(callback);
  194. }
  195. void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
  196. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  197. if (body_func_.IsEmpty()) {
  198. LOG(ERROR) << "Tried to read twice from a JSChunkedDataPipeGetter";
  199. // Drop the handle on the floor.
  200. return;
  201. }
  202. data_producer_ = std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
  203. v8::HandleScope handle_scope(isolate_);
  204. auto maybe_wrapper = GetWrapper(isolate_);
  205. v8::Local<v8::Value> wrapper;
  206. if (!maybe_wrapper.ToLocal(&wrapper)) {
  207. return;
  208. }
  209. v8::Local<v8::Value> argv[] = {wrapper};
  210. node::Environment* env = node::Environment::GetCurrent(isolate_);
  211. auto global = env->context()->Global();
  212. node::MakeCallback(isolate_, global, body_func_.Get(isolate_),
  213. node::arraysize(argv), argv, {0, 0});
  214. }
  215. v8::Local<v8::Promise> WriteChunk(v8::Local<v8::Value> buffer_val) {
  216. gin_helper::Promise<void> promise(isolate_);
  217. v8::Local<v8::Promise> handle = promise.GetHandle();
  218. if (!buffer_val->IsArrayBufferView()) {
  219. promise.RejectWithErrorMessage("Expected an ArrayBufferView");
  220. return handle;
  221. }
  222. if (is_writing_) {
  223. promise.RejectWithErrorMessage("Only one write can be pending at a time");
  224. return handle;
  225. }
  226. if (!size_callback_) {
  227. promise.RejectWithErrorMessage("Can't write after calling done()");
  228. return handle;
  229. }
  230. auto buffer = buffer_val.As<v8::ArrayBufferView>();
  231. is_writing_ = true;
  232. bytes_written_ += buffer->ByteLength();
  233. data_producer_->Write(
  234. std::make_unique<BufferDataSource>(buffer),
  235. base::BindOnce(&JSChunkedDataPipeGetter::OnWriteChunkComplete,
  236. // We're OK to use Unretained here because we own
  237. // |data_producer_|.
  238. base::Unretained(this), std::move(promise)));
  239. return handle;
  240. }
  241. void OnWriteChunkComplete(gin_helper::Promise<void> promise,
  242. MojoResult result) {
  243. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  244. is_writing_ = false;
  245. if (result == MOJO_RESULT_OK) {
  246. promise.Resolve();
  247. } else {
  248. promise.RejectWithErrorMessage("mojo result not ok: " +
  249. base::NumberToString(result));
  250. Finished();
  251. }
  252. }
  253. // TODO(nornagon): accept a net error here to allow the data provider to
  254. // cancel the request with an error.
  255. void Done() {
  256. if (size_callback_) {
  257. std::move(size_callback_).Run(net::OK, bytes_written_);
  258. Finished();
  259. }
  260. }
  261. void Finished() {
  262. body_func_.Reset();
  263. data_producer_.reset();
  264. receiver_.reset();
  265. size_callback_.Reset();
  266. }
  267. SEQUENCE_CHECKER(sequence_checker_);
  268. GetSizeCallback size_callback_;
  269. mojo::Receiver<network::mojom::ChunkedDataPipeGetter> receiver_{this};
  270. std::unique_ptr<mojo::DataPipeProducer> data_producer_;
  271. bool is_writing_ = false;
  272. uint64_t bytes_written_ = 0;
  273. raw_ptr<v8::Isolate> isolate_;
  274. v8::Global<v8::Function> body_func_;
  275. };
  276. gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
  277. gin::kEmbedderNativeGin};
  278. const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
  279. net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
  280. semantics {
  281. sender: "Electron Net module"
  282. description:
  283. "Issue HTTP/HTTPS requests using Chromium's native networking "
  284. "library."
  285. trigger: "Using the Net module"
  286. data: "Anything the user wants to send."
  287. destination: OTHER
  288. }
  289. policy {
  290. cookies_allowed: YES
  291. cookies_store: "user"
  292. setting: "This feature cannot be disabled."
  293. })");
  294. } // namespace
  295. gin::WrapperInfo SimpleURLLoaderWrapper::kWrapperInfo = {
  296. gin::kEmbedderNativeGin};
  297. SimpleURLLoaderWrapper::SimpleURLLoaderWrapper(
  298. ElectronBrowserContext* browser_context,
  299. std::unique_ptr<network::ResourceRequest> request,
  300. int options)
  301. : browser_context_(browser_context),
  302. request_options_(options),
  303. request_(std::move(request)) {
  304. DETACH_FROM_SEQUENCE(sequence_checker_);
  305. if (!request_->trusted_params)
  306. request_->trusted_params = network::ResourceRequest::TrustedParams();
  307. bool create_network_observer = true;
  308. if (electron::IsUtilityProcess()) {
  309. create_network_observer =
  310. !URLLoaderBundle::GetInstance()
  311. ->ShouldUseNetworkObserverfromURLLoaderFactory();
  312. }
  313. if (create_network_observer) {
  314. mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
  315. url_loader_network_observer_remote;
  316. url_loader_network_observer_receivers_.Add(
  317. this,
  318. url_loader_network_observer_remote.InitWithNewPipeAndPassReceiver());
  319. request_->trusted_params->url_loader_network_observer =
  320. std::move(url_loader_network_observer_remote);
  321. }
  322. // Chromium filters headers using browser rules, while for net module we have
  323. // every header passed. The following setting will allow us to capture the
  324. // raw headers in the URLLoader.
  325. request_->trusted_params->report_raw_headers = true;
  326. Start();
  327. }
  328. void SimpleURLLoaderWrapper::Start() {
  329. // Make a copy of the request; we'll need to re-send it if we get redirected.
  330. auto request = std::make_unique<network::ResourceRequest>();
  331. *request = *request_;
  332. // SimpleURLLoader has no way to set a data pipe as the request body, which
  333. // we need to do for streaming upload, so instead we "cheat" and pretend to
  334. // SimpleURLLoader like there is no request_body when we construct it. Later,
  335. // we will sneakily put the request_body back while it isn't looking.
  336. scoped_refptr<network::ResourceRequestBody> request_body =
  337. std::move(request->request_body);
  338. network::ResourceRequest* request_ref = request.get();
  339. loader_ =
  340. network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
  341. if (request_body)
  342. request_ref->request_body = std::move(request_body);
  343. loader_->SetAllowHttpErrorResults(true);
  344. loader_->SetURLLoaderFactoryOptions(request_options_);
  345. loader_->SetOnResponseStartedCallback(base::BindOnce(
  346. &SimpleURLLoaderWrapper::OnResponseStarted, weak_factory_.GetWeakPtr()));
  347. loader_->SetOnRedirectCallback(base::BindRepeating(
  348. &SimpleURLLoaderWrapper::OnRedirect, weak_factory_.GetWeakPtr()));
  349. loader_->SetOnUploadProgressCallback(base::BindRepeating(
  350. &SimpleURLLoaderWrapper::OnUploadProgress, weak_factory_.GetWeakPtr()));
  351. loader_->SetOnDownloadProgressCallback(base::BindRepeating(
  352. &SimpleURLLoaderWrapper::OnDownloadProgress, weak_factory_.GetWeakPtr()));
  353. url_loader_factory_ = GetURLLoaderFactoryForURL(request_ref->url);
  354. loader_->DownloadAsStream(url_loader_factory_.get(), this);
  355. }
  356. void SimpleURLLoaderWrapper::Pin() {
  357. // Prevent ourselves from being GC'd until the request is complete. Must be
  358. // called after gin::CreateHandle, otherwise the wrapper isn't initialized.
  359. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  360. pinned_wrapper_.Reset(isolate, GetWrapper(isolate).ToLocalChecked());
  361. }
  362. void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
  363. pinned_chunk_pipe_getter_.Reset(JavascriptEnvironment::GetIsolate(),
  364. body_getter);
  365. }
  366. SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() = default;
  367. void SimpleURLLoaderWrapper::OnAuthRequired(
  368. const std::optional<base::UnguessableToken>& window_id,
  369. int32_t request_id,
  370. const GURL& url,
  371. bool first_auth_attempt,
  372. const net::AuthChallengeInfo& auth_info,
  373. const scoped_refptr<net::HttpResponseHeaders>& head_headers,
  374. mojo::PendingRemote<network::mojom::AuthChallengeResponder>
  375. auth_challenge_responder) {
  376. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  377. mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
  378. std::move(auth_challenge_responder));
  379. // WeakPtr because if we're Cancel()ed while waiting for auth, and the
  380. // network service also decides to cancel at the same time and kill this
  381. // pipe, we might end up trying to call Cancel again on dead memory.
  382. auth_responder.set_disconnect_handler(base::BindOnce(
  383. &SimpleURLLoaderWrapper::Cancel, weak_factory_.GetWeakPtr()));
  384. auto cb = base::BindOnce(
  385. [](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
  386. gin::Arguments* args) {
  387. std::u16string username_str, password_str;
  388. if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
  389. auth_responder->OnAuthCredentials(std::nullopt);
  390. return;
  391. }
  392. auth_responder->OnAuthCredentials(
  393. net::AuthCredentials(username_str, password_str));
  394. },
  395. std::move(auth_responder));
  396. Emit("login", auth_info, std::move(cb));
  397. }
  398. void SimpleURLLoaderWrapper::OnSSLCertificateError(
  399. const GURL& url,
  400. int net_error,
  401. const net::SSLInfo& ssl_info,
  402. bool fatal,
  403. OnSSLCertificateErrorCallback response) {
  404. std::move(response).Run(net_error);
  405. }
  406. void SimpleURLLoaderWrapper::OnClearSiteData(
  407. const GURL& url,
  408. const std::string& header_value,
  409. int32_t load_flags,
  410. const std::optional<net::CookiePartitionKey>& cookie_partition_key,
  411. bool partitioned_state_allowed_only,
  412. OnClearSiteDataCallback callback) {
  413. std::move(callback).Run();
  414. }
  415. void SimpleURLLoaderWrapper::OnLoadingStateUpdate(
  416. network::mojom::LoadInfoPtr info,
  417. OnLoadingStateUpdateCallback callback) {
  418. std::move(callback).Run();
  419. }
  420. void SimpleURLLoaderWrapper::OnSharedStorageHeaderReceived(
  421. const url::Origin& request_origin,
  422. std::vector<network::mojom::SharedStorageModifierMethodWithOptionsPtr>
  423. methods,
  424. const std::optional<std::string>& with_lock,
  425. OnSharedStorageHeaderReceivedCallback callback) {
  426. std::move(callback).Run();
  427. }
  428. void SimpleURLLoaderWrapper::Clone(
  429. mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
  430. observer) {
  431. url_loader_network_observer_receivers_.Add(this, std::move(observer));
  432. }
  433. void SimpleURLLoaderWrapper::Cancel() {
  434. loader_.reset();
  435. pinned_wrapper_.Reset();
  436. pinned_chunk_pipe_getter_.Reset();
  437. // This ensures that no further callbacks will be called, so there's no need
  438. // for additional guards.
  439. }
  440. scoped_refptr<network::SharedURLLoaderFactory>
  441. SimpleURLLoaderWrapper::GetURLLoaderFactoryForURL(const GURL& url) {
  442. if (electron::IsUtilityProcess())
  443. return URLLoaderBundle::GetInstance()->GetSharedURLLoaderFactory();
  444. CHECK(browser_context_);
  445. // Explicitly handle intercepted protocols here, even though
  446. // ProxyingURLLoaderFactory would handle them later on, so that we can
  447. // correctly intercept file:// scheme URLs.
  448. if (const bool bypass = request_options_ & kBypassCustomProtocolHandlers;
  449. !bypass) {
  450. const std::string_view scheme = url.scheme_piece();
  451. const auto* const protocol_registry =
  452. ProtocolRegistry::FromBrowserContext(browser_context_);
  453. if (const auto* const protocol_handler =
  454. protocol_registry->FindIntercepted(scheme)) {
  455. return network::SharedURLLoaderFactory::Create(
  456. std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
  457. ElectronURLLoaderFactory::Create(protocol_handler->first,
  458. protocol_handler->second)));
  459. }
  460. if (const auto* const protocol_handler =
  461. protocol_registry->FindRegistered(scheme)) {
  462. return network::SharedURLLoaderFactory::Create(
  463. std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
  464. ElectronURLLoaderFactory::Create(protocol_handler->first,
  465. protocol_handler->second)));
  466. }
  467. }
  468. if (url.SchemeIsFile()) {
  469. return network::SharedURLLoaderFactory::Create(
  470. std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
  471. AsarURLLoaderFactory::Create()));
  472. }
  473. return browser_context_->GetURLLoaderFactory();
  474. }
  475. // static
  476. gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
  477. gin::Arguments* args) {
  478. gin_helper::Dictionary opts;
  479. if (!args->GetNext(&opts)) {
  480. args->ThrowTypeError("Expected a dictionary");
  481. return {};
  482. }
  483. auto request = std::make_unique<network::ResourceRequest>();
  484. opts.Get("method", &request->method);
  485. opts.Get("url", &request->url);
  486. if (!request->url.is_valid()) {
  487. args->ThrowTypeError("Invalid URL");
  488. return {};
  489. }
  490. request->site_for_cookies = net::SiteForCookies::FromUrl(request->url);
  491. opts.Get("referrer", &request->referrer);
  492. request->referrer_policy =
  493. blink::ReferrerUtils::GetDefaultNetReferrerPolicy();
  494. opts.Get("referrerPolicy", &request->referrer_policy);
  495. std::string origin;
  496. opts.Get("origin", &origin);
  497. if (!origin.empty()) {
  498. request->request_initiator = url::Origin::Create(GURL(origin));
  499. }
  500. bool has_user_activation;
  501. if (opts.Get("hasUserActivation", &has_user_activation)) {
  502. request->trusted_params = network::ResourceRequest::TrustedParams();
  503. request->trusted_params->has_user_activation = has_user_activation;
  504. }
  505. if (std::string mode; opts.Get("mode", &mode)) {
  506. using Val = network::mojom::RequestMode;
  507. static constexpr auto Lookup =
  508. base::MakeFixedFlatMap<std::string_view, Val>({
  509. {"cors", Val::kCors},
  510. {"navigate", Val::kNavigate},
  511. {"no-cors", Val::kNoCors},
  512. {"same-origin", Val::kSameOrigin},
  513. });
  514. if (auto iter = Lookup.find(mode); iter != Lookup.end())
  515. request->mode = iter->second;
  516. }
  517. if (std::string destination; opts.Get("destination", &destination)) {
  518. using Val = network::mojom::RequestDestination;
  519. static constexpr auto Lookup =
  520. base::MakeFixedFlatMap<std::string_view, Val>({
  521. {"audio", Val::kAudio},
  522. {"audioworklet", Val::kAudioWorklet},
  523. {"document", Val::kDocument},
  524. {"embed", Val::kEmbed},
  525. {"empty", Val::kEmpty},
  526. {"font", Val::kFont},
  527. {"frame", Val::kFrame},
  528. {"iframe", Val::kIframe},
  529. {"image", Val::kImage},
  530. {"manifest", Val::kManifest},
  531. {"object", Val::kObject},
  532. {"paintworklet", Val::kPaintWorklet},
  533. {"report", Val::kReport},
  534. {"script", Val::kScript},
  535. {"serviceworker", Val::kServiceWorker},
  536. {"style", Val::kStyle},
  537. {"track", Val::kTrack},
  538. {"video", Val::kVideo},
  539. {"worker", Val::kWorker},
  540. {"xslt", Val::kXslt},
  541. });
  542. if (auto iter = Lookup.find(destination); iter != Lookup.end())
  543. request->destination = iter->second;
  544. }
  545. bool credentials_specified =
  546. opts.Get("credentials", &request->credentials_mode);
  547. std::vector<std::pair<std::string, std::string>> extra_headers;
  548. if (opts.Get("extraHeaders", &extra_headers)) {
  549. for (const auto& it : extra_headers) {
  550. if (!net::HttpUtil::IsValidHeaderName(it.first) ||
  551. !net::HttpUtil::IsValidHeaderValue(it.second)) {
  552. args->ThrowTypeError("Invalid header name or value");
  553. return {};
  554. }
  555. request->headers.SetHeader(it.first, it.second);
  556. }
  557. }
  558. blink::mojom::FetchCacheMode cache_mode =
  559. blink::mojom::FetchCacheMode::kDefault;
  560. opts.Get("cache", &cache_mode);
  561. switch (cache_mode) {
  562. case blink::mojom::FetchCacheMode::kNoStore:
  563. request->load_flags |= net::LOAD_DISABLE_CACHE;
  564. break;
  565. case blink::mojom::FetchCacheMode::kValidateCache:
  566. request->load_flags |= net::LOAD_VALIDATE_CACHE;
  567. break;
  568. case blink::mojom::FetchCacheMode::kBypassCache:
  569. request->load_flags |= net::LOAD_BYPASS_CACHE;
  570. break;
  571. case blink::mojom::FetchCacheMode::kForceCache:
  572. request->load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
  573. break;
  574. case blink::mojom::FetchCacheMode::kOnlyIfCached:
  575. request->load_flags |=
  576. net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
  577. break;
  578. case blink::mojom::FetchCacheMode::kUnspecifiedOnlyIfCachedStrict:
  579. request->load_flags |= net::LOAD_ONLY_FROM_CACHE;
  580. break;
  581. case blink::mojom::FetchCacheMode::kDefault:
  582. break;
  583. case blink::mojom::FetchCacheMode::kUnspecifiedForceCacheMiss:
  584. request->load_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_BYPASS_CACHE;
  585. break;
  586. }
  587. bool use_session_cookies = false;
  588. opts.Get("useSessionCookies", &use_session_cookies);
  589. int options = network::mojom::kURLLoadOptionSniffMimeType;
  590. if (!credentials_specified && !use_session_cookies) {
  591. // This is the default case, as well as the case when credentials is not
  592. // specified and useSessionCookies is false. credentials_mode will be
  593. // kInclude, but cookies will be blocked.
  594. request->credentials_mode = network::mojom::CredentialsMode::kInclude;
  595. options |= network::mojom::kURLLoadOptionBlockAllCookies;
  596. }
  597. bool bypass_custom_protocol_handlers = false;
  598. opts.Get("bypassCustomProtocolHandlers", &bypass_custom_protocol_handlers);
  599. if (bypass_custom_protocol_handlers)
  600. options |= kBypassCustomProtocolHandlers;
  601. v8::Local<v8::Value> body;
  602. v8::Local<v8::Value> chunk_pipe_getter;
  603. if (opts.Get("body", &body)) {
  604. if (body->IsArrayBufferView()) {
  605. auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
  606. request_body->AppendBytes(ToVec<uint8_t>(body.As<v8::ArrayBufferView>()));
  607. request->request_body = std::move(request_body);
  608. } else if (body->IsFunction()) {
  609. auto body_func = body.As<v8::Function>();
  610. mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
  611. data_pipe_getter;
  612. chunk_pipe_getter = JSChunkedDataPipeGetter::Create(
  613. args->isolate(), body_func,
  614. data_pipe_getter.InitWithNewPipeAndPassReceiver())
  615. .ToV8();
  616. request->request_body =
  617. base::MakeRefCounted<network::ResourceRequestBody>();
  618. request->request_body->SetAllowHTTP1ForStreamingUpload(true);
  619. request->request_body->SetToChunkedDataPipe(
  620. std::move(data_pipe_getter),
  621. network::ResourceRequestBody::ReadOnlyOnce(false));
  622. }
  623. }
  624. ElectronBrowserContext* browser_context = nullptr;
  625. if (electron::IsBrowserProcess()) {
  626. std::string partition;
  627. gin::Handle<Session> session;
  628. if (!opts.Get("session", &session)) {
  629. if (opts.Get("partition", &partition))
  630. session = Session::FromPartition(args->isolate(), partition);
  631. else // default session
  632. session = Session::FromPartition(args->isolate(), "");
  633. }
  634. browser_context = session->browser_context();
  635. }
  636. auto ret = gin::CreateHandle(
  637. args->isolate(),
  638. new SimpleURLLoaderWrapper(browser_context, std::move(request), options));
  639. ret->Pin();
  640. if (!chunk_pipe_getter.IsEmpty()) {
  641. ret->PinBodyGetter(chunk_pipe_getter);
  642. }
  643. return ret;
  644. }
  645. void SimpleURLLoaderWrapper::OnDataReceived(std::string_view string_view,
  646. base::OnceClosure resume) {
  647. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  648. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  649. v8::HandleScope handle_scope(isolate);
  650. auto array_buffer = v8::ArrayBuffer::New(isolate, string_view.size());
  651. memcpy(array_buffer->Data(), string_view.data(), string_view.size());
  652. Emit("data", array_buffer, std::move(resume));
  653. }
  654. void SimpleURLLoaderWrapper::OnComplete(bool success) {
  655. auto self = weak_factory_.GetWeakPtr();
  656. if (success) {
  657. Emit("complete");
  658. } else {
  659. Emit("error", net::ErrorToString(loader_->NetError()));
  660. }
  661. // If users initiate process shutdown when the event is emitted, then
  662. // we would perform cleanup of the wrapper and we should bail out below.
  663. if (self) {
  664. loader_.reset();
  665. pinned_wrapper_.Reset();
  666. pinned_chunk_pipe_getter_.Reset();
  667. }
  668. }
  669. void SimpleURLLoaderWrapper::OnResponseStarted(
  670. const GURL& final_url,
  671. const network::mojom::URLResponseHead& response_head) {
  672. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  673. v8::HandleScope scope(isolate);
  674. auto dict = gin::Dictionary::CreateEmpty(isolate);
  675. dict.Set("statusCode", response_head.headers->response_code());
  676. dict.Set("statusMessage", response_head.headers->GetStatusText());
  677. dict.Set("httpVersion", response_head.headers->GetHttpVersion());
  678. dict.Set("headers", response_head.headers.get());
  679. dict.Set("rawHeaders", response_head.raw_response_headers);
  680. dict.Set("mimeType", response_head.mime_type);
  681. Emit("response-started", final_url, dict);
  682. }
  683. void SimpleURLLoaderWrapper::OnRedirect(
  684. const GURL& url_before_redirect,
  685. const net::RedirectInfo& redirect_info,
  686. const network::mojom::URLResponseHead& response_head,
  687. std::vector<std::string>* removed_headers) {
  688. Emit("redirect", redirect_info, response_head.headers.get());
  689. if (!loader_)
  690. // The redirect was aborted by JS.
  691. return;
  692. // Optimization: if both the old and new URLs are handled by the network
  693. // service, just FollowRedirect.
  694. if (network::IsURLHandledByNetworkService(redirect_info.new_url) &&
  695. network::IsURLHandledByNetworkService(request_->url))
  696. return;
  697. // Otherwise, restart the request (potentially picking a new
  698. // URLLoaderFactory). See
  699. // https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=534-550;drc=fbaec92ad5982f83aa4544d5c88d66d08034a9f4
  700. bool should_clear_upload = false;
  701. net::RedirectUtil::UpdateHttpRequest(
  702. request_->url, request_->method, redirect_info, *removed_headers,
  703. /* modified_headers = */ std::nullopt, &request_->headers,
  704. &should_clear_upload);
  705. if (should_clear_upload) {
  706. // The request body is no longer applicable.
  707. request_->request_body.reset();
  708. }
  709. request_->url = redirect_info.new_url;
  710. request_->method = redirect_info.new_method;
  711. request_->site_for_cookies = redirect_info.new_site_for_cookies;
  712. // See if navigation network isolation key needs to be updated.
  713. request_->trusted_params->isolation_info =
  714. request_->trusted_params->isolation_info.CreateForRedirect(
  715. url::Origin::Create(request_->url));
  716. request_->referrer = GURL(redirect_info.new_referrer);
  717. request_->referrer_policy = redirect_info.new_referrer_policy;
  718. request_->navigation_redirect_chain.push_back(redirect_info.new_url);
  719. Start();
  720. }
  721. void SimpleURLLoaderWrapper::OnUploadProgress(uint64_t position,
  722. uint64_t total) {
  723. Emit("upload-progress", position, total);
  724. }
  725. void SimpleURLLoaderWrapper::OnDownloadProgress(uint64_t current) {
  726. Emit("download-progress", current);
  727. }
  728. // static
  729. gin::ObjectTemplateBuilder SimpleURLLoaderWrapper::GetObjectTemplateBuilder(
  730. v8::Isolate* isolate) {
  731. return gin_helper::EventEmitterMixin<
  732. SimpleURLLoaderWrapper>::GetObjectTemplateBuilder(isolate)
  733. .SetMethod("cancel", &SimpleURLLoaderWrapper::Cancel);
  734. }
  735. const char* SimpleURLLoaderWrapper::GetTypeName() {
  736. return "SimpleURLLoaderWrapper";
  737. }
  738. void SimpleURLLoaderWrapper::WillBeDestroyed() {
  739. ClearWeak();
  740. }
  741. } // namespace electron::api