text: // Copyright (c) 2019 GitHub, Inc.
  // Use of this source code is governed by the MIT license that can be
  // found in the LICENSE file.
  
  #include "shell/browser/net/proxying_url_loader_factory.h"
  
  #include <utility>
  
  #include "base/command_line.h"
  #include "base/strings/string_split.h"
  #include "base/strings/string_util.h"
  #include "content/public/browser/browser_context.h"
  #include "extensions/browser/extension_navigation_ui_data.h"
  #include "net/base/completion_repeating_callback.h"
  #include "net/base/load_flags.h"
  #include "net/http/http_status_code.h"
  #include "net/http/http_util.h"
  #include "services/metrics/public/cpp/ukm_source_id.h"
  #include "services/network/public/cpp/features.h"
  #include "shell/browser/net/asar/asar_url_loader.h"
  #include "shell/common/options_switches.h"
  
  namespace electron {
  
  ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
      FollowRedirectParams() = default;
  ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
      ~FollowRedirectParams() = default;
  
  ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
      ProxyingURLLoaderFactory* factory,
      int64_t web_request_id,
      int32_t view_routing_id,
      int32_t frame_routing_id,
      int32_t network_service_request_id,
      uint32_t options,
      const network::ResourceRequest& request,
      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
      mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
      mojo::PendingRemote<network::mojom::URLLoaderClient> client)
      : factory_(factory),
        request_(request),
        original_initiator_(request.request_initiator),
        request_id_(web_request_id),
        view_routing_id_(view_routing_id),
        frame_routing_id_(frame_routing_id),
        network_service_request_id_(network_service_request_id),
        options_(options),
        traffic_annotation_(traffic_annotation),
        proxied_loader_receiver_(this, std::move(loader_receiver)),
        target_client_(std::move(client)),
        current_response_(network::mojom::URLResponseHead::New()),
        // Always use "extraHeaders" mode to be compatible with old APIs, except
        // when the |request_id_| is zero, which is not supported in Chromium and
        // only happens in Electron when the request is started from net module.
        has_any_extra_headers_listeners_(network_service_request_id != 0) {
    // If there is a client error, clean up the request.
    target_client_.set_disconnect_handler(base::BindOnce(
        &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError,
        weak_factory_.GetWeakPtr(),
        network::URLLoaderCompletionStatus(net::ERR_ABORTED)));
    proxied_loader_receiver_.set_disconnect_handler(base::BindOnce(
        &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError,
        weak_factory_.GetWeakPtr(),
        network::URLLoaderCompletionStatus(net::ERR_ABORTED)));
  }
  
  ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
      ProxyingURLLoaderFactory* factory,
      uint64_t request_id,
      int32_t frame_routing_id,
      const network::ResourceRequest& request)
      : factory_(factory),
        request_(request),
        original_initiator_(request.request_initiator),
        request_id_(request_id),
        frame_routing_id_(frame_routing_id),
        proxied_loader_receiver_(this),
        for_cors_preflight_(true),
        has_any_extra_headers_listeners_(true) {}
  
  ProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {
    // This is important to ensure that no outstanding blocking requests continue
    // to reference state owned by this object.
    if (info_) {
      factory_->web_request_api()->OnRequestWillBeDestroyed(&info_.value());
    }
    if (on_before_send_headers_callback_) {
      std::move(on_before_send_headers_callback_)
          .Run(net::ERR_ABORTED, base::nullopt);
    }
    if (on_headers_received_callback_) {
      std::move(on_headers_received_callback_)
          .Run(net::ERR_ABORTED, base::nullopt, base::nullopt);
    }
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::Restart() {
    UpdateRequestInfo();
    RestartInternal();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::UpdateRequestInfo() {
    // Derive a new WebRequestInfo value any time |Restart()| is called, because
    // the details in |request_| may have changed e.g. if we've been redirected.
    // |request_initiator| can be modified on redirects, but we keep the original
    // for |initiator| in the event. See also
    // https://developer.chrome.com/extensions/webRequest#event-onBeforeRequest.
    network::ResourceRequest request_for_info = request_;
    request_for_info.request_initiator = original_initiator_;
    info_.emplace(extensions::WebRequestInfoInitParams(
        request_id_, factory_->render_process_id_, frame_routing_id_,
        factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy()
                                      : nullptr,
        view_routing_id_, request_for_info, false,
        !(options_ & network::mojom::kURLLoadOptionSynchronous),
        factory_->IsForServiceWorkerScript(), factory_->navigation_id_,
        ukm::kInvalidSourceIdObj));
  
    current_request_uses_header_client_ =
        factory_->url_loader_header_client_receiver_.is_bound() &&
        (for_cors_preflight_ || network_service_request_id_ != 0) &&
        has_any_extra_headers_listeners_;
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::RestartInternal() {
    DCHECK_EQ(info_->url, request_.url)
        << "UpdateRequestInfo must have been called first";
  
    // If the header client will be used, we start the request immediately, and
    // OnBeforeSendHeaders and OnSendHeaders will be handled there. Otherwise,
    // send these events before the request starts.
    base::RepeatingCallback<void(int)> continuation;
    if (current_request_uses_header_client_) {
      continuation = base::BindRepeating(
          &InProgressRequest::ContinueToStartRequest, weak_factory_.GetWeakPtr());
    } else if (for_cors_preflight_) {
      // In this case we do nothing because extensions should see nothing.
      return;
    } else {
      continuation =
          base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders,
                              weak_factory_.GetWeakPtr());
    }
    redirect_url_ = GURL();
    int result = factory_->web_request_api()->OnBeforeRequest(
        &info_.value(), request_, continuation, &redirect_url_);
    if (result == net::ERR_BLOCKED_BY_CLIENT) {
      // The request was cancelled synchronously. Dispatch an error notification
      // and terminate the request.
      network::URLLoaderCompletionStatus status(result);
      OnRequestError(status);
      return;
    }
  
    if (result == net::ERR_IO_PENDING) {
      // One or more listeners is blocking, so the request must be paused until
      // they respond. |continuation| above will be invoked asynchronously to
      // continue or cancel the request.
      //
      // We pause the binding here to prevent further client message processing.
      if (proxied_client_receiver_.is_bound())
        proxied_client_receiver_.Pause();
  
      // Pause the header client, since we want to wait until OnBeforeRequest has
      // finished before processing any future events.
      if (header_client_receiver_.is_bound())
        header_client_receiver_.Pause();
      return;
    }
    DCHECK_EQ(net::OK, result);
  
    continuation.Run(net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::FollowRedirect(
      const std::vector<std::string>& removed_headers,
      const net::HttpRequestHeaders& modified_headers,
      const net::HttpRequestHeaders& modified_cors_exempt_headers,
      const base::Optional<GURL>& new_url) {
    if (new_url)
      request_.url = new_url.value();
  
    for (const std::string& header : removed_headers)
      request_.headers.RemoveHeader(header);
    request_.headers.MergeFrom(modified_headers);
  
    // Call this before checking |current_request_uses_header_client_| as it
    // calculates it.
    UpdateRequestInfo();
  
    if (target_loader_.is_bound()) {
      // If header_client_ is used, then we have to call FollowRedirect now as
      // that's what triggers the network service calling back to
      // OnBeforeSendHeaders(). Otherwise, don't call FollowRedirect now. Wait for
      // the onBeforeSendHeaders callback(s) to run as these may modify request
      // headers and if so we'll pass these modifications to FollowRedirect.
      if (current_request_uses_header_client_) {
        target_loader_->FollowRedirect(removed_headers, modified_headers,
                                       modified_cors_exempt_headers, new_url);
      } else {
        auto params = std::make_unique<FollowRedirectParams>();
        params->removed_headers = removed_headers;
        params->modified_headers = modified_headers;
        params->modified_cors_exempt_headers = modified_cors_exempt_headers;
        params->new_url = new_url;
        pending_follow_redirect_params_ = std::move(params);
      }
    }
  
    RestartInternal();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::SetPriority(
      net::RequestPriority priority,
      int32_t intra_priority_value) {
    if (target_loader_.is_bound())
      target_loader_->SetPriority(priority, intra_priority_value);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::PauseReadingBodyFromNet() {
    if (target_loader_.is_bound())
      target_loader_->PauseReadingBodyFromNet();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ResumeReadingBodyFromNet() {
    if (target_loader_.is_bound())
      target_loader_->ResumeReadingBodyFromNet();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse(
      network::mojom::URLResponseHeadPtr head) {
    if (current_request_uses_header_client_) {
      // Use the headers we got from OnHeadersReceived as that'll contain
      // Set-Cookie if it existed.
      auto saved_headers = current_response_->headers;
      current_response_ = std::move(head);
      current_response_->headers = saved_headers;
      ContinueToResponseStarted(net::OK);
    } else {
      current_response_ = std::move(head);
      HandleResponseOrRedirectHeaders(
          base::BindOnce(&InProgressRequest::ContinueToResponseStarted,
                         weak_factory_.GetWeakPtr()));
    }
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveEarlyHints(
      network::mojom::EarlyHintsPtr early_hints) {
    target_client_->OnReceiveEarlyHints(std::move(early_hints));
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
      const net::RedirectInfo& redirect_info,
      network::mojom::URLResponseHeadPtr head) {
    // Note: In Electron we don't check IsRedirectSafe.
  
    if (current_request_uses_header_client_) {
      // Use the headers we got from OnHeadersReceived as that'll contain
      // Set-Cookie if it existed.
      auto saved_headers = current_response_->headers;
      current_response_ = std::move(head);
      // If this redirect is from an HSTS upgrade, OnHeadersReceived will not be
      // called before OnReceiveRedirect, so make sure the saved headers exist
      // before setting them.
      if (saved_headers)
        current_response_->headers = saved_headers;
      ContinueToBeforeRedirect(redirect_info, net::OK);
    } else {
      current_response_ = std::move(head);
      HandleResponseOrRedirectHeaders(
          base::BindOnce(&InProgressRequest::ContinueToBeforeRedirect,
                         weak_factory_.GetWeakPtr(), redirect_info));
    }
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnUploadProgress(
      int64_t current_position,
      int64_t total_size,
      OnUploadProgressCallback callback) {
    target_client_->OnUploadProgress(current_position, total_size,
                                     std::move(callback));
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnReceiveCachedMetadata(
      mojo_base::BigBuffer data) {
    target_client_->OnReceiveCachedMetadata(std::move(data));
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnTransferSizeUpdated(
      int32_t transfer_size_diff) {
    target_client_->OnTransferSizeUpdated(transfer_size_diff);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnStartLoadingResponseBody(
      mojo::ScopedDataPipeConsumerHandle body) {
    target_client_->OnStartLoadingResponseBody(std::move(body));
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnComplete(
      const network::URLLoaderCompletionStatus& status) {
    if (status.error_code != net::OK) {
      OnRequestError(status);
      return;
    }
  
    target_client_->OnComplete(status);
    factory_->web_request_api()->OnCompleted(&info_.value(), request_,
                                             status.error_code);
  
    // Deletes |this|.
    factory_->RemoveRequest(network_service_request_id_, request_id_);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated(
      mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
    // When CORS is involved there may be multiple network::URLLoader associated
    // with this InProgressRequest, because CorsURLLoader may create a new
    // network::URLLoader for the same request id in redirect handling - see
    // CorsURLLoader::FollowRedirect. In such a case the old network::URLLoader
    // is going to be detached fairly soon, so we don't need to take care of it.
    // We need this explicit reset to avoid a DCHECK failure in mojo::Receiver.
    header_client_receiver_.reset();
  
    header_client_receiver_.Bind(std::move(receiver));
    if (for_cors_preflight_) {
      // In this case we don't have |target_loader_| and
      // |proxied_client_binding_|, and |receiver| is the only connection to the
      // network service, so we observe mojo connection errors.
      header_client_receiver_.set_disconnect_handler(base::BindOnce(
          &ProxyingURLLoaderFactory::InProgressRequest::OnRequestError,
          weak_factory_.GetWeakPtr(),
          network::URLLoaderCompletionStatus(net::ERR_FAILED)));
    }
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnBeforeSendHeaders(
      const net::HttpRequestHeaders& headers,
      OnBeforeSendHeadersCallback callback) {
    if (!current_request_uses_header_client_) {
      std::move(callback).Run(net::OK, base::nullopt);
      return;
    }
  
    request_.headers = headers;
    on_before_send_headers_callback_ = std::move(callback);
    ContinueToBeforeSendHeaders(net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnHeadersReceived(
      const std::string& headers,
      const net::IPEndPoint& remote_endpoint,
      OnHeadersReceivedCallback callback) {
    if (!current_request_uses_header_client_) {
      std::move(callback).Run(net::OK, base::nullopt, GURL());
  
      if (for_cors_preflight_) {
        // CORS preflight is supported only when "extraHeaders" is specified.
        // Deletes |this|.
        factory_->RemoveRequest(network_service_request_id_, request_id_);
      }
      return;
    }
  
    on_headers_received_callback_ = std::move(callback);
    current_response_ = network::mojom::URLResponseHead::New();
    current_response_->headers =
        base::MakeRefCounted<net::HttpResponseHeaders>(headers);
    current_response_->remote_endpoint = remote_endpoint;
    HandleResponseOrRedirectHeaders(
        base::BindOnce(&InProgressRequest::ContinueToHandleOverrideHeaders,
                       weak_factory_.GetWeakPtr()));
  }
  
  void ProxyingURLLoaderFactory::OnLoaderForCorsPreflightCreated(
      const network::ResourceRequest& request,
      mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
    // Please note that the URLLoader is now starting, without waiting for
    // additional signals from here. The URLLoader will be blocked before
    // sending HTTP request headers (TrustedHeaderClient.OnBeforeSendHeaders),
    // but the connection set up will be done before that. This is acceptable from
    // Web Request API because the extension has already allowed to set up
    // a connection to the same URL (i.e., the actual request), and distinguishing
    // two connections for the actual request and the preflight request before
    // sending request headers is very difficult.
    const uint64_t web_request_id = ++(*request_id_generator_);
  
    auto result = requests_.insert(std::make_pair(
        web_request_id, std::make_unique<InProgressRequest>(
                            this, web_request_id, frame_routing_id_, request)));
  
    result.first->second->OnLoaderCreated(std::move(receiver));
    result.first->second->Restart();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeSendHeaders(
      int error_code) {
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    if (!current_request_uses_header_client_ && !redirect_url_.is_empty()) {
      if (for_cors_preflight_) {
        // CORS preflight doesn't support redirect.
        OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED));
        return;
      }
      HandleBeforeRequestRedirect();
      return;
    }
  
    if (proxied_client_receiver_.is_bound())
      proxied_client_receiver_.Resume();
  
    auto continuation = base::BindRepeating(
        &InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr());
    // Note: In Electron onBeforeSendHeaders is called for all protocols.
    int result = factory_->web_request_api()->OnBeforeSendHeaders(
        &info_.value(), request_, continuation, &request_.headers);
  
    if (result == net::ERR_BLOCKED_BY_CLIENT) {
      // The request was cancelled synchronously. Dispatch an error notification
      // and terminate the request.
      OnRequestError(network::URLLoaderCompletionStatus(result));
      return;
    }
  
    if (result == net::ERR_IO_PENDING) {
      // One or more listeners is blocking, so the request must be paused until
      // they respond. |continuation| above will be invoked asynchronously to
      // continue or cancel the request.
      //
      // We pause the receiver here to prevent further client message processing.
      if (proxied_client_receiver_.is_bound())
        proxied_client_receiver_.Resume();
      return;
    }
    DCHECK_EQ(net::OK, result);
  
    ContinueToSendHeaders(std::set<std::string>(), std::set<std::string>(),
                          net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ContinueToSendHeaders(
      const std::set<std::string>& removed_headers,
      const std::set<std::string>& set_headers,
      int error_code) {
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    if (current_request_uses_header_client_) {
      DCHECK(on_before_send_headers_callback_);
      std::move(on_before_send_headers_callback_)
          .Run(error_code, request_.headers);
    } else if (pending_follow_redirect_params_) {
      pending_follow_redirect_params_->removed_headers.insert(
          pending_follow_redirect_params_->removed_headers.end(),
          removed_headers.begin(), removed_headers.end());
  
      for (auto& set_header : set_headers) {
        std::string header_value;
        if (request_.headers.GetHeader(set_header, &header_value)) {
          pending_follow_redirect_params_->modified_headers.SetHeader(
              set_header, header_value);
        } else {
          NOTREACHED();
        }
      }
  
      if (target_loader_.is_bound()) {
        target_loader_->FollowRedirect(
            pending_follow_redirect_params_->removed_headers,
            pending_follow_redirect_params_->modified_headers,
            pending_follow_redirect_params_->modified_cors_exempt_headers,
            pending_follow_redirect_params_->new_url);
      }
  
      pending_follow_redirect_params_.reset();
    }
  
    if (proxied_client_receiver_.is_bound())
      proxied_client_receiver_.Resume();
  
    // Note: In Electron onSendHeaders is called for all protocols.
    factory_->web_request_api()->OnSendHeaders(&info_.value(), request_,
                                               request_.headers);
  
    if (!current_request_uses_header_client_)
      ContinueToStartRequest(net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ContinueToStartRequest(
      int error_code) {
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    if (current_request_uses_header_client_ && !redirect_url_.is_empty()) {
      HandleBeforeRequestRedirect();
      return;
    }
  
    if (proxied_client_receiver_.is_bound())
      proxied_client_receiver_.Resume();
  
    if (header_client_receiver_.is_bound())
      header_client_receiver_.Resume();
  
    if (for_cors_preflight_) {
      // For CORS preflight requests, we have already started the request in
      // the network service. We did block the request by blocking
      // |header_client_receiver_|, which we unblocked right above.
      return;
    }
  
    if (!target_loader_.is_bound() && factory_->target_factory_.is_bound()) {
      // No extensions have cancelled us up to this point, so it's now OK to
      // initiate the real network request.
      uint32_t options = options_;
      // Even if this request does not use the header client, future redirects
      // might, so we need to set the option on the loader.
      if (has_any_extra_headers_listeners_)
        options |= network::mojom::kURLLoadOptionUseHeaderClient;
      factory_->target_factory_->CreateLoaderAndStart(
          mojo::MakeRequest(&target_loader_), network_service_request_id_,
          options, request_, proxied_client_receiver_.BindNewPipeAndPassRemote(),
          traffic_annotation_);
    }
  
    // From here the lifecycle of this request is driven by subsequent events on
    // either |proxy_loader_binding_|, |proxy_client_receiver_|, or
    // |header_client_receiver_|.
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::
      ContinueToHandleOverrideHeaders(int error_code) {
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    DCHECK(on_headers_received_callback_);
    base::Optional<std::string> headers;
    if (override_headers_) {
      headers = override_headers_->raw_headers();
      if (current_request_uses_header_client_) {
        // Make sure to update current_response_,  since when OnReceiveResponse
        // is called we will not use its headers as it might be missing the
        // Set-Cookie line (as that gets stripped over IPC).
        current_response_->headers = override_headers_;
      }
    }
  
    if (for_cors_preflight_ && !redirect_url_.is_empty()) {
      OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED));
      return;
    }
  
    std::move(on_headers_received_callback_).Run(net::OK, headers, redirect_url_);
    override_headers_ = nullptr;
  
    if (for_cors_preflight_) {
      // If this is for CORS preflight, there is no associated client.
      info_->AddResponseInfoFromResourceResponse(*current_response_);
      // Do not finish proxied preflight requests that require proxy auth.
      // The request is not finished yet, give control back to network service
      // which will start authentication process.
      if (info_->response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED)
        return;
      // We notify the completion here, and delete |this|.
      factory_->web_request_api()->OnResponseStarted(&info_.value(), request_);
      factory_->web_request_api()->OnCompleted(&info_.value(), request_, net::OK);
  
      factory_->RemoveRequest(network_service_request_id_, request_id_);
      return;
    }
  
    if (proxied_client_receiver_.is_bound())
      proxied_client_receiver_.Resume();
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ContinueToResponseStarted(
      int error_code) {
    DCHECK(!for_cors_preflight_);
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    DCHECK(!current_request_uses_header_client_ || !override_headers_);
    if (override_headers_)
      current_response_->headers = override_headers_;
  
    std::string redirect_location;
    if (override_headers_ && override_headers_->IsRedirect(&redirect_location)) {
      // The response headers may have been overridden by an |onHeadersReceived|
      // handler and may have been changed to a redirect. We handle that here
      // instead of acting like regular request completion.
      //
      // Note that we can't actually change how the Network Service handles the
      // original request at this point, so our "redirect" is really just
      // generating an artificial |onBeforeRedirect| event and starting a new
      // request to the Network Service. Our client shouldn't know the difference.
      GURL new_url(redirect_location);
  
      net::RedirectInfo redirect_info;
      redirect_info.status_code = override_headers_->response_code();
      redirect_info.new_method = request_.method;
      redirect_info.new_url = new_url;
      redirect_info.new_site_for_cookies = net::SiteForCookies::FromUrl(new_url);
  
      // These will get re-bound if a new request is initiated by
      // |FollowRedirect()|.
      proxied_client_receiver_.reset();
      header_client_receiver_.reset();
      target_loader_.reset();
  
      ContinueToBeforeRedirect(redirect_info, net::OK);
      return;
    }
  
    info_->AddResponseInfoFromResourceResponse(*current_response_);
  
    proxied_client_receiver_.Resume();
  
    factory_->web_request_api()->OnResponseStarted(&info_.value(), request_);
    target_client_->OnReceiveResponse(current_response_.Clone());
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeRedirect(
      const net::RedirectInfo& redirect_info,
      int error_code) {
    if (error_code != net::OK) {
      OnRequestError(network::URLLoaderCompletionStatus(error_code));
      return;
    }
  
    info_->AddResponseInfoFromResourceResponse(*current_response_);
  
    if (proxied_client_receiver_.is_bound())
      proxied_client_receiver_.Resume();
  
    factory_->web_request_api()->OnBeforeRedirect(&info_.value(), request_,
                                                  redirect_info.new_url);
    target_client_->OnReceiveRedirect(redirect_info, current_response_.Clone());
    request_.url = redirect_info.new_url;
    request_.method = redirect_info.new_method;
    request_.site_for_cookies = redirect_info.new_site_for_cookies;
    request_.referrer = GURL(redirect_info.new_referrer);
    request_.referrer_policy = redirect_info.new_referrer_policy;
  
    // The request method can be changed to "GET". In this case we need to
    // reset the request body manually.
    if (request_.method == net::HttpRequestHeaders::kGetMethod)
      request_.request_body = nullptr;
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::
      HandleBeforeRequestRedirect() {
    // The extension requested a redirect. Close the connection with the current
    // URLLoader and inform the URLLoaderClient the WebRequest API generated a
    // redirect. To load |redirect_url_|, a new URLLoader will be recreated
    // after receiving FollowRedirect().
  
    // Forgetting to close the connection with the current URLLoader caused
    // bugs. The latter doesn't know anything about the redirect. Continuing
    // the load with it gives unexpected results. See
    // https://crbug.com/882661#c72.
    proxied_client_receiver_.reset();
    header_client_receiver_.reset();
    target_loader_.reset();
  
    constexpr int kInternalRedirectStatusCode = 307;
  
    net::RedirectInfo redirect_info;
    redirect_info.status_code = kInternalRedirectStatusCode;
    redirect_info.new_method = request_.method;
    redirect_info.new_url = redirect_url_;
    redirect_info.new_site_for_cookies =
        net::SiteForCookies::FromUrl(redirect_url_);
  
    auto head = network::mojom::URLResponseHead::New();
    std::string headers = base::StringPrintf(
        "HTTP/1.1 %i Internal Redirect\n"
        "Location: %s\n"
        "Non-Authoritative-Reason: WebRequest API\n\n",
        kInternalRedirectStatusCode, redirect_url_.spec().c_str());
  
    // Cross-origin requests need to modify the Origin header to 'null'. Since
    // CorsURLLoader sets |request_initiator| to the Origin request header in
    // NetworkService, we need to modify |request_initiator| here to craft the
    // Origin header indirectly.
    // Following checks implement the step 10 of "4.4. HTTP-redirect fetch",
    // https://fetch.spec.whatwg.org/#http-redirect-fetch
    if (request_.request_initiator &&
        (!url::Origin::Create(redirect_url_)
              .IsSameOriginWith(url::Origin::Create(request_.url)) &&
         !request_.request_initiator->IsSameOriginWith(
             url::Origin::Create(request_.url)))) {
      // Reset the initiator to pretend tainted origin flag of the spec is set.
      request_.request_initiator = url::Origin();
    }
    head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
        net::HttpUtil::AssembleRawHeaders(headers));
    head->encoded_data_length = 0;
  
    current_response_ = std::move(head);
    ContinueToBeforeRedirect(redirect_info, net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::
      HandleResponseOrRedirectHeaders(net::CompletionOnceCallback continuation) {
    override_headers_ = nullptr;
    redirect_url_ = GURL();
  
    info_->AddResponseInfoFromResourceResponse(*current_response_);
  
    net::CompletionRepeatingCallback copyable_callback =
        base::AdaptCallbackForRepeating(std::move(continuation));
    DCHECK(info_.has_value());
    int result = factory_->web_request_api()->OnHeadersReceived(
        &info_.value(), request_, copyable_callback,
        current_response_->headers.get(), &override_headers_, &redirect_url_);
    if (result == net::ERR_BLOCKED_BY_CLIENT) {
      OnRequestError(network::URLLoaderCompletionStatus(result));
      return;
    }
  
    if (result == net::ERR_IO_PENDING) {
      // One or more listeners is blocking, so the request must be paused until
      // they respond. |continuation| above will be invoked asynchronously to
      // continue or cancel the request.
      //
      // We pause the receiver here to prevent further client message processing.
      if (proxied_client_receiver_.is_bound())
        proxied_client_receiver_.Pause();
      return;
    }
  
    DCHECK_EQ(net::OK, result);
  
    copyable_callback.Run(net::OK);
  }
  
  void ProxyingURLLoaderFactory::InProgressRequest::OnRequestError(
      const network::URLLoaderCompletionStatus& status) {
    if (target_client_)
      target_client_->OnComplete(status);
    factory_->web_request_api()->OnErrorOccurred(&info_.value(), request_,
                                                 status.error_code);
  
    // Deletes |this|.
    factory_->RemoveRequest(network_service_request_id_, request_id_);
  }
  
  ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
      WebRequestAPI* web_request_api,
      const HandlersMap& intercepted_handlers,
      int render_process_id,
      int frame_routing_id,
      int view_routing_id,
      uint64_t* request_id_generator,
      std::unique_ptr<extensions::ExtensionNavigationUIData> navigation_ui_data,
      base::Optional<int64_t> navigation_id,
      network::mojom::URLLoaderFactoryRequest loader_request,
      mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote,
      mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
          header_client_receiver,
      content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type)
      : web_request_api_(web_request_api),
        intercepted_handlers_(intercepted_handlers),
        render_process_id_(render_process_id),
        frame_routing_id_(frame_routing_id),
        view_routing_id_(view_routing_id),
        request_id_generator_(request_id_generator),
        navigation_ui_data_(std::move(navigation_ui_data)),
        navigation_id_(std::move(navigation_id)),
        loader_factory_type_(loader_factory_type) {
    target_factory_.Bind(std::move(target_factory_remote));
    target_factory_.set_disconnect_handler(base::BindOnce(
        &ProxyingURLLoaderFactory::OnTargetFactoryError, base::Unretained(this)));
    proxy_receivers_.Add(this, std::move(loader_request));
    proxy_receivers_.set_disconnect_handler(base::BindRepeating(
        &ProxyingURLLoaderFactory::OnProxyBindingError, base::Unretained(this)));
  
    if (header_client_receiver)
      url_loader_header_client_receiver_.Bind(std::move(header_client_receiver));
  
    ignore_connections_limit_domains_ = base::SplitString(
        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kIgnoreConnectionsLimit),
        ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  }
  
  ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
  
  bool ProxyingURLLoaderFactory::ShouldIgnoreConnectionsLimit(
      const network::ResourceRequest& request) {
    for (const auto& domain : ignore_connections_limit_domains_) {
      if (request.url.DomainIs(domain)) {
        return true;
      }
    }
    return false;
  }
  
  void ProxyingURLLoaderFactory::CreateLoaderAndStart(
      mojo::PendingReceiver<network::mojom::URLLoader> loader,
      int32_t request_id,
      uint32_t options,
      const network::ResourceRequest& original_request,
      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
    // Take a copy so we can mutate the request.
    network::ResourceRequest request = original_request;
  
    if (ShouldIgnoreConnectionsLimit(request)) {
      request.priority = net::RequestPriority::MAXIMUM_PRIORITY;
      request.load_flags |= net::LOAD_IGNORE_LIMITS;
    }
  
    // Check if user has intercepted this scheme.
    auto it = intercepted_handlers_.find(request.url.scheme());
    if (it != intercepted_handlers_.end()) {
      mojo::PendingRemote<network::mojom::URLLoaderFactory> loader_remote;
      this->Clone(loader_remote.InitWithNewPipeAndPassReceiver());
  
      // <scheme, <type, handler>>
      it->second.second.Run(
          request, base::BindOnce(&ElectronURLLoaderFactory::StartLoading,
                                  std::move(loader), request_id, options, request,
                                  std::move(client), traffic_annotation,
                                  std::move(loader_remote), it->second.first));
      return;
    }
  
    // The loader of ServiceWorker forbids loading scripts from file:// URLs, and
    // Chromium does not provide a way to override this behavior. So in order to
    // make ServiceWorker work with file:// URLs, we have to intercept its
    // requests here.
    if (IsForServiceWorkerScript() && request.url.SchemeIsFile()) {
      asar::CreateAsarURLLoader(request, std::move(loader), std::move(client),
                                new net::HttpResponseHeaders(""));
      return;
    }
  
    if (!web_request_api()->HasListener()) {
      // Pass-through to the original factory.
      target_factory_->CreateLoaderAndStart(std::move(loader), request_id,
                                            options, request, std::move(client),
                                            traffic_annotation);
      return;
    }
  
    // The request ID doesn't really matter. It just needs to be unique
    // per-BrowserContext so extensions can make sense of it.  Note that
    // |network_service_request_id_| by contrast is not necessarily unique, so we
    // don't use it for identity here.
    const uint64_t web_request_id = ++(*request_id_generator_);
  
    // Notes: Chromium assumes that requests with zero-ID would never use the
    // "extraHeaders" code path, however in Electron requests started from
    // the net module would have zero-ID because they do not have renderer process
    // associated.
    if (request_id)
      network_request_id_to_web_request_id_.emplace(request_id, web_request_id);
  
    auto result = requests_.emplace(
        web_request_id,
        std::make_unique<InProgressRequest>(
            this, web_request_id, view_routing_id_, frame_routing_id_, request_id,
            options, request, traffic_annotation, std::move(loader),
            std::move(client)));
    result.first->second->Restart();
  }
  
  void ProxyingURLLoaderFactory::Clone(
      mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver) {
    proxy_receivers_.Add(this, std::move(loader_receiver));
  }
  
  void ProxyingURLLoaderFactory::OnLoaderCreated(
      int32_t request_id,
      mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
    auto it = network_request_id_to_web_request_id_.find(request_id);
    if (it == network_request_id_to_web_request_id_.end())
      return;
  
    auto request_it = requests_.find(it->second);
    DCHECK(request_it != requests_.end());
    request_it->second->OnLoaderCreated(std::move(receiver));
  }
  
  bool ProxyingURLLoaderFactory::IsForServiceWorkerScript() const {
    return loader_factory_type_ == content::ContentBrowserClient::
                                       URLLoaderFactoryType::kServiceWorkerScript;
  }
  
  void ProxyingURLLoaderFactory::OnTargetFactoryError() {
    target_factory_.reset();
    proxy_receivers_.Clear();
  
    MaybeDeleteThis();
  }
  
  void ProxyingURLLoaderFactory::OnProxyBindingError() {
    if (proxy_receivers_.empty())
      target_factory_.reset();
  
    MaybeDeleteThis();
  }
  
  void ProxyingURLLoaderFactory::RemoveRequest(int32_t network_service_request_id,
                                               uint64_t request_id) {
    network_request_id_to_web_request_id_.erase(network_service_request_id);
    requests_.erase(request_id);
  
    MaybeDeleteThis();
  }
  
  void ProxyingURLLoaderFactory::MaybeDeleteThis() {
    // Even if all URLLoaderFactory pipes connected to this object have been
    // closed it has to stay alive until all active requests have completed.
    if (target_factory_.is_bound() || !requests_.empty() ||
        !proxy_receivers_.empty())
      return;
  
    delete this;
  }
  
  }  // namespace electron
  