1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215

content / public / test / url_loader_interceptor.h [blame]

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_
#define CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>

#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"

namespace network {
class URLLoaderFactoryBuilder;
}  // namespace network

namespace content {

// Helper class to intercept URLLoaderFactory calls for tests.
// This intercepts:
//   -frame requests (which start from the browser)
//   -subresource requests from pages, dedicated workers, and shared workers
//     -by sending the renderer an intermediate URLLoaderFactory
//   -subresource requests from service workers and requests of non-installed
//    service worker scripts
//     -at EmbeddedWorkerInstance
//   -requests by the browser
//   -http(s)://mock.failed.request/foo URLs internally, copying the behavior
//    of net::URLRequestFailedJob
//
// Prefer not to use this class. In order of ease of use & simplicity:
//  -if you need to serve static data, use net::test::EmbeddedTestServer and
//   serve data from the source tree (e.g. in content/test/data).
//  -if you need to control the response data at runtime, then use
//   net::test_server::EmbeddedTestServer::RegisterRequestHandler.
//  -if you need to delay when the server sends the response, use
//   net::test_server::ControllableHttpResponse.
//  -otherwise, if you need full control over the net::Error and/or want to
//   inspect and/or modify the C++ structs used by URLLoader interface, then use
//   this helper class.
//
// Notes:
//  -the callback is called on the UI or IO threads depending on the factory
//   that was hooked
//    -this is done to avoid changing message order
//  -intercepting resource requests for subresources changes message order by
//   definition (since they would normally go directly from renderer->network
//   service, but now they're routed through the browser).
class URLLoaderInterceptor {
 public:
  struct RequestParams {
    RequestParams();
    ~RequestParams();
    RequestParams(RequestParams&& other);
    RequestParams& operator=(RequestParams&& other);
    // See the comment for `url_loader_factory::TerminalParams::process_id_`.
    int process_id;
    // The following are the parameters to CreateLoaderAndStart.
    mojo::PendingReceiver<network::mojom::URLLoader> receiver;
    int32_t request_id;
    uint32_t options;
    network::ResourceRequest url_request;
    mojo::Remote<network::mojom::URLLoaderClient> client;
    net::MutableNetworkTrafficAnnotationTag traffic_annotation;
  };
  // Function signature for intercept method.
  // Return true if the request was intercepted. Otherwise this class will
  // forward the request to the original URLLoaderFactory.
  using InterceptCallback =
      base::RepeatingCallback<bool(RequestParams* params)>;

  // Function signature for a loading completion method.
  // This class will listen on loading completion responses from the network,
  // invoke this callback, and delegate the response to the original client.
  using URLLoaderCompletionStatusCallback = base::RepeatingCallback<void(
      const GURL& request_url,
      const network::URLLoaderCompletionStatus& status)>;

  // Create an interceptor which calls |callback|. If |ready_callback| is not
  // provided, a nested RunLoop is used to ensure the interceptor is ready
  // before returning. If |ready_callback| is provided, no RunLoop is called,
  // and instead |ready_callback| is called after the interceptor is installed.
  // If provided, |completion_status_callback| is called when the load
  // completes.
  //
  // In order to hook up `completion_status_callback`, the interceptor wraps all
  // requests that the `intercept_callback` does not intercept, so destroying
  // the URLLoaderInterceptor aborts all non-intercepted requests.
  explicit URLLoaderInterceptor(
      InterceptCallback intercept_callback,
      const URLLoaderCompletionStatusCallback& completion_status_callback = {},
      base::OnceClosure ready_callback = {});

  URLLoaderInterceptor(const URLLoaderInterceptor&) = delete;
  URLLoaderInterceptor& operator=(const URLLoaderInterceptor&) = delete;

  ~URLLoaderInterceptor();

  // Serves static data, similar to net::test::EmbeddedTestServer, for
  // cases where you need a static origin, such as tests with origin trials.
  // Optional callback will notify callers for any accessed urls.
  static std::unique_ptr<URLLoaderInterceptor> ServeFilesFromDirectoryAtOrigin(
      const std::string& relative_base_path,
      const GURL& origin,
      base::RepeatingCallback<void(const GURL&)> callback = base::DoNothing());

  // Helper methods for use when intercepting.
  // Writes the given response body, header, and SSL Info to `client`.
  // If `url` is present, also computes the ParsedHeaders for the response.
  static void WriteResponse(std::string_view headers,
                            std::string_view body,
                            network::mojom::URLLoaderClient* client,
                            std::optional<net::SSLInfo> ssl_info = std::nullopt,
                            std::optional<GURL> url = std::nullopt);

  // Reads the given path, relative to the root source directory, and writes it
  // to |client|. For headers:
  //   1) if |headers| is specified, it's used
  //   2) otherwise if an adjoining file that ends in .mock-http-headers is
  //      found, its contents will be used
  //   3) otherwise a simple 200 response will be used, with a Content-Type
  //      guessed from the file extension
  // For SSL info, if |ssl_info| is specified, then it is added to the response.
  // If `url` is present, also computes the ParsedHeaders for the response.
  static void WriteResponse(const std::string& relative_path,
                            network::mojom::URLLoaderClient* client,
                            const std::string* headers = nullptr,
                            std::optional<net::SSLInfo> ssl_info = std::nullopt,
                            std::optional<GURL> url = std::nullopt);

  // Like above, but uses an absolute file path.
  static void WriteResponse(const base::FilePath& file_path,
                            network::mojom::URLLoaderClient* client,
                            const std::string* headers = nullptr,
                            std::optional<net::SSLInfo> ssl_info = std::nullopt,
                            std::optional<GURL> url = std::nullopt);

  // Returns an interceptor that (as long as it says alive) will intercept
  // requests to |url| and fail them using the provided |error|.
  // |ready_callback| is optional and avoids the use of RunLoop, see
  // the constructor for more detail.
  static std::unique_ptr<URLLoaderInterceptor> SetupRequestFailForURL(
      const GURL& url,
      net::Error error,
      base::OnceClosure ready_callback = {});

  // Returns the URL of the last request processed by this interceptor.
  //
  // Use this function instead of creating a WebContentsObserver to observe
  // request headers, if you need the last request url sent in the event of
  // resends or redirects, as the NavigationHandle::GetRequestHeaders() function
  // only returns the initial request's request headers.
  const GURL& GetLastRequestURL();

  // Returns the request headers of the last request processed by this
  // interceptor.
  //
  // Use this function instead of creating a WebContentsObserver to observe
  // request headers, if you need the last request headers sent in the event of
  // resends or redirects, as the NavigationHandle::GetRequestHeaders() function
  // only returns the initial request's request headers.
  const net::HttpRequestHeaders& GetLastRequestHeaders();

 private:
  class IOState;
  class Interceptor;
  class Wrapper;

  // Adds `this` as an interceptor when a `URLLoaderFactory` is about to be
  // created. `Wrapper` plumbs related objects to `Intercept()`.
  void InterceptorCallback(int process_id,
                           network::URLLoaderFactoryBuilder& factory_builder);

  // Attempts to intercept the given request, returning true if it was
  // intercepted.
  bool Intercept(RequestParams* params);

  // Called on IO thread at initialization and shutdown.
  void InitializeOnIOThread(base::OnceClosure closure);

  // Sets the request URL of the last request processed by this interceptor.
  void SetLastRequestURL(const GURL& url);

  // Sets the request headers of the last request processed by this interceptor.
  void SetLastRequestHeaders(const net::HttpRequestHeaders& headers);

  bool use_runloop_;
  base::OnceClosure ready_callback_;
  InterceptCallback callback_;
  scoped_refptr<IOState> io_thread_;
  // For intercepting non-frame requests from the browser process. There is one
  // per StoragePartition. Only accessed on UI thread.
  std::set<std::unique_ptr<Wrapper>> wrappers_on_ui_thread_;

  base::Lock last_request_lock_;
  GURL last_request_url_ GUARDED_BY(last_request_lock_);
  net::HttpRequestHeaders last_request_headers_ GUARDED_BY(last_request_lock_);
};

}  // namespace content

#endif  // CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_