blob: 35b26d6acd629166fdf26c8dd546cd9afe4ca42c [file] [log] [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_