Commit b8d512a4 authored by Tsuyoshi Horo's avatar Tsuyoshi Horo Committed by Commit Bot

Initial implementation of SignedExchangeHandler and WebPackage response handling.

This CL implements the following classes:
 - SignedExchangeHandler
 - WebPackageLoader
 - WebPackageRequestHandler

But Currenly SignedExchangeHandler doesn't implement any CBOR parsing logic nor
verifying logic. It just behaves as if the passed body is a signed HTTP exchange
which contains a request to "https://example.com/test.html" and a response with
a payload which is equal to the original body.

The added LayoutTest "origin-signed-response.html" must pass only when
SignedHTTPExchange feature is enabled.

Bug: 803774

Change-Id: I53d397d7cdfe79d48d6891db65ab1a1e12100b92
Reviewed-on: https://chromium-review.googlesource.com/869872
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#531921}
parent 33d0f6fa
......@@ -1012,6 +1012,8 @@ jumbo_source_set("browser") {
"loader/resource_requester_info.h",
"loader/resource_scheduler_filter.cc",
"loader/resource_scheduler_filter.h",
"loader/signed_exchange_handler.cc",
"loader/signed_exchange_handler.h",
"loader/stream_resource_handler.cc",
"loader/stream_resource_handler.h",
"loader/stream_writer.cc",
......@@ -1028,6 +1030,10 @@ jumbo_source_set("browser") {
"loader/url_loader_request_handler.h",
"loader/wake_lock_resource_throttle.cc",
"loader/wake_lock_resource_throttle.h",
"loader/web_package_loader.cc",
"loader/web_package_loader.h",
"loader/web_package_request_handler.cc",
"loader/web_package_request_handler.h",
"loader_delegate_impl.cc",
"loader_delegate_impl.h",
"locks/lock_manager.cc",
......
......@@ -600,7 +600,8 @@ void AppCacheRequestHandler::MaybeCreateLoader(
bool AppCacheRequestHandler::MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request) {
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) {
// The sync interface of this method is inherited from the
// URLLoaderRequestHandler class. The LoaderCallback created here is invoked
// synchronously in fallback cases, and only when there really is a loader
......
......@@ -93,7 +93,8 @@ class CONTENT_EXPORT AppCacheRequestHandler
bool MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request) override;
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) override;
base::Optional<SubresourceLoaderParams> MaybeCreateSubresourceLoaderParams()
override;
......
......@@ -8,6 +8,7 @@
#include <vector>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
......@@ -20,6 +21,7 @@
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/loader/stream_resource_handler.h"
#include "content/browser/loader/web_package_request_handler.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_save_info.h"
......@@ -27,6 +29,7 @@
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/common/content_features.h"
#include "content/public/common/webplugininfo.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_sniffer.h"
......@@ -414,6 +417,10 @@ bool MimeSniffingResourceHandler::MaybeStartInterception() {
if (!must_download) {
if (blink::IsSupportedMimeType(mime_type))
return true;
if (base::FeatureList::IsEnabled(features::kSignedHTTPExchange) &&
WebPackageRequestHandler::IsSupportedMimeType(mime_type)) {
return true;
}
bool handled_by_plugin;
if (!CheckForPluginHandler(&handled_by_plugin))
......
......@@ -22,6 +22,7 @@
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/loader/url_loader_request_handler.h"
#include "content/browser/loader/web_package_request_handler.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/service_worker/service_worker_navigation_handle.h"
#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
......@@ -64,6 +65,11 @@ namespace content {
namespace {
bool IsRequestHandlerEnabled() {
return base::FeatureList::IsEnabled(features::kNetworkService) ||
base::FeatureList::IsEnabled(features::kSignedHTTPExchange);
}
// Request ID for browser initiated requests. We start at -2 on the same lines
// as ResourceDispatcherHostImpl.
int g_next_request_id = -2;
......@@ -177,6 +183,61 @@ bool IsDownload(const network::ResourceResponse& response,
response.head.headers->response_code() / 100 == 2);
}
std::unique_ptr<network::ResourceRequest> CreateResourceRequest(
NavigationRequestInfo* request_info,
int frame_tree_node_id,
bool allow_download) {
// TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
auto new_request = std::make_unique<network::ResourceRequest>();
new_request->method = request_info->common_params.method;
new_request->url = request_info->common_params.url;
new_request->site_for_cookies = request_info->site_for_cookies;
new_request->priority = net::HIGHEST;
new_request->render_frame_id = frame_tree_node_id;
// The code below to set fields like request_initiator, referrer, etc has
// been copied from ResourceDispatcherHostImpl. We did not refactor the
// common code into a function, because RDHI uses accessor functions on the
// URLRequest class to set these fields. whereas we use ResourceRequest here.
new_request->request_initiator = request_info->begin_params->initiator_origin;
new_request->referrer = request_info->common_params.referrer.url;
new_request->referrer_policy = Referrer::ReferrerPolicyForUrlRequest(
request_info->common_params.referrer.policy);
new_request->headers.AddHeadersFromString(
request_info->begin_params->headers);
new_request->headers.SetHeader(network::kAcceptHeader,
network::kFrameAcceptHeader);
new_request->resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
if (request_info->is_main_frame)
new_request->update_first_party_url_on_redirect = true;
int load_flags = request_info->begin_params->load_flags;
load_flags |= net::LOAD_VERIFY_EV_CERT;
if (request_info->is_main_frame)
load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED;
// Sync loads should have maximum priority and should be the only
// requests that have the ignore limits flag set.
DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS));
new_request->load_flags = load_flags;
new_request->request_body = request_info->common_params.post_data.get();
new_request->report_raw_headers = request_info->report_raw_headers;
new_request->allow_download = allow_download;
new_request->enable_load_timing = true;
new_request->fetch_request_mode = network::mojom::FetchRequestMode::kNavigate;
new_request->fetch_credentials_mode =
network::mojom::FetchCredentialsMode::kInclude;
new_request->fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual;
return new_request;
}
} // namespace
// Kept around during the lifetime of the navigation request, and is
......@@ -242,6 +303,11 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
DCHECK(!base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK_CURRENTLY_ON(BrowserThread::IO);
default_loader_used_ = true;
if (base::FeatureList::IsEnabled(features::kSignedHTTPExchange)) {
handlers_.push_back(base::MakeUnique<WebPackageRequestHandler>());
}
// The ResourceDispatcherHostImpl can be null in unit tests.
if (ResourceDispatcherHostImpl::Get()) {
ResourceDispatcherHostImpl::Get()->BeginNavigationRequest(
......@@ -363,12 +429,16 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
handlers_.push_back(std::move(appcache_handler));
}
if (base::FeatureList::IsEnabled(features::kSignedHTTPExchange)) {
handlers_.push_back(base::MakeUnique<WebPackageRequestHandler>());
}
Restart();
}
// This could be called multiple times to follow a chain of redirects.
void Restart() {
DCHECK(base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK(IsRequestHandlerEnabled());
// Clear |url_loader_| if it's not the default one (network). This allows
// the restarted request to use a new loader, instead of, e.g., reusing the
// AppCache or service worker loader. For an optimization, we keep and reuse
......@@ -387,15 +457,17 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
// if the |handler| wants to handle the request.
void MaybeStartLoader(URLLoaderRequestHandler* handler,
StartLoaderCallback start_loader_callback) {
DCHECK(base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK(IsRequestHandlerEnabled());
if (start_loader_callback) {
// |handler| wants to handle the request.
DCHECK(handler);
default_loader_used_ = false;
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
std::move(start_loader_callback),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_, navigation_ui_data_.get()),
base::FeatureList::IsEnabled(features::kNetworkService)
? GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_, navigation_ui_data_.get())
: std::vector<std::unique_ptr<content::URLLoaderThrottle>>(),
frame_tree_node_id_, resource_request_.get(), this,
kNavigationUrlLoaderTrafficAnnotation,
base::ThreadTaskRunnerHandle::Get());
......@@ -479,13 +551,8 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
void FollowRedirect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!redirect_info_.new_url.is_empty());
DCHECK(!response_url_loader_);
DCHECK(url_loader_);
// TODO(arthursonzogni): We might need to go through the rest of the
// function once there are several types of URLLoader handling the
// navigation, even in non network-service mode.
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
if (!IsRequestHandlerEnabled()) {
url_loader_->FollowRedirect();
return;
}
......@@ -554,7 +621,7 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
bool is_download;
bool is_stream;
std::unique_ptr<NavigationData> cloned_navigation_data;
if (base::FeatureList::IsEnabled(features::kNetworkService)) {
if (IsRequestHandlerEnabled()) {
is_download = IsDownload(*response.get(), url_, url_chain_,
initiator_origin_, suggested_filename_);
is_stream = false;
......@@ -663,7 +730,7 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
// different response. For e.g. AppCache may have fallback content.
bool MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response) {
if (!base::FeatureList::IsEnabled(features::kNetworkService))
if (!IsRequestHandlerEnabled())
return false;
if (!default_loader_used_)
......@@ -672,7 +739,8 @@ class NavigationURLLoaderNetworkService::URLLoaderRequestController
for (auto& handler : handlers_) {
network::mojom::URLLoaderClientRequest response_client_request;
if (handler->MaybeCreateLoaderForResponse(response, &response_url_loader_,
&response_client_request)) {
&response_client_request,
url_loader_.get())) {
response_loader_binding_.Bind(std::move(response_client_request));
default_loader_used_ = false;
url_loader_.reset();
......@@ -781,12 +849,18 @@ NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService(
AppCacheNavigationHandleCore* appcache_handle_core =
appcache_handle ? appcache_handle->core() : nullptr;
std::unique_ptr<network::ResourceRequest> new_request;
if (IsRequestHandlerEnabled()) {
new_request = CreateResourceRequest(request_info.get(), frame_tree_node_id,
allow_download_);
}
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
DCHECK(!request_controller_);
request_controller_ = std::make_unique<URLLoaderRequestController>(
/* initial_handlers = */
std::vector<std::unique_ptr<URLLoaderRequestHandler>>(),
/* resource_request = */ nullptr, resource_context,
std::move(new_request), resource_context,
/* default_url_factory_getter = */ nullptr,
request_info->common_params.url,
request_info->begin_params->initiator_origin,
......@@ -807,55 +881,6 @@ NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService(
return;
}
// TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
auto new_request = std::make_unique<network::ResourceRequest>();
new_request->method = request_info->common_params.method;
new_request->url = request_info->common_params.url;
new_request->site_for_cookies = request_info->site_for_cookies;
new_request->priority = net::HIGHEST;
new_request->render_frame_id = frame_tree_node_id;
// The code below to set fields like request_initiator, referrer, etc has
// been copied from ResourceDispatcherHostImpl. We did not refactor the
// common code into a function, because RDHI uses accessor functions on the
// URLRequest class to set these fields. whereas we use ResourceRequest here.
new_request->request_initiator = request_info->begin_params->initiator_origin;
new_request->referrer = request_info->common_params.referrer.url;
new_request->referrer_policy = Referrer::ReferrerPolicyForUrlRequest(
request_info->common_params.referrer.policy);
new_request->headers.AddHeadersFromString(
request_info->begin_params->headers);
new_request->headers.SetHeader(network::kAcceptHeader,
network::kFrameAcceptHeader);
new_request->resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
if (request_info->is_main_frame)
new_request->update_first_party_url_on_redirect = true;
int load_flags = request_info->begin_params->load_flags;
load_flags |= net::LOAD_VERIFY_EV_CERT;
if (request_info->is_main_frame)
load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED;
// Sync loads should have maximum priority and should be the only
// requests that have the ignore limits flag set.
DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS));
new_request->load_flags = load_flags;
new_request->request_body = request_info->common_params.post_data.get();
new_request->report_raw_headers = request_info->report_raw_headers;
new_request->allow_download = allow_download_;
new_request->enable_load_timing = true;
new_request->fetch_request_mode = network::mojom::FetchRequestMode::kNavigate;
new_request->fetch_credentials_mode =
network::mojom::FetchCredentialsMode::kInclude;
new_request->fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual;
// Check if a web UI scheme wants to handle this request.
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
......
......@@ -63,7 +63,8 @@ class TestURLLoaderRequestHandler : public URLLoaderRequestHandler {
bool MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request) override {
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) override {
return false;
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/signed_exchange_handler.h"
#include "base/feature_list.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/system/string_data_pipe_producer.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
namespace content {
namespace {
constexpr size_t kPipeSizeForSignedResponseBody = 65536;
} // namespace
SignedExchangeHandler::SignedExchangeHandler(
mojo::ScopedDataPipeConsumerHandle body)
: body_(std::move(body)) {
DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
}
SignedExchangeHandler::~SignedExchangeHandler() = default;
void SignedExchangeHandler::GetHTTPExchange(
ExchangeFoundCallback found_callback,
ExchangeFinishedCallback finish_callback) {
DCHECK(!found_callback_);
DCHECK(!finish_callback_);
found_callback_ = std::move(found_callback);
finish_callback_ = std::move(finish_callback);
drainer_.reset(new mojo::common::DataPipeDrainer(this, std::move(body_)));
}
void SignedExchangeHandler::OnDataAvailable(const void* data,
size_t num_bytes) {
original_body_string_.append(static_cast<const char*>(data), num_bytes);
}
void SignedExchangeHandler::OnDataComplete() {
if (!found_callback_)
return;
// TODO(https://crbug.com/80374): Get the request url by parsing CBOR format.
GURL request_url = GURL("https://example.com/test.html");
// TODO(https://crbug.com/80374): Get the request method by parsing CBOR
// format.
std::string request_method = "GET";
// TODO(https://crbug.com/80374): Get the payload by parsing CBOR format.
std::string payload = original_body_string_;
original_body_string_.clear();
// TODO(https://crbug.com/80374): Get more headers by parsing CBOR.
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
network::ResourceResponseHead resource_response;
resource_response.headers = headers;
resource_response.mime_type = "text/html";
// TODO(https://crbug.com/80374): Get the certificate by parsing CBOR and
// verify.
base::Optional<net::SSLInfo> ssl_info;
mojo::DataPipe pipe(kPipeSizeForSignedResponseBody);
// TODO(https://crbug.com/80374): Write the error handling code. This could
// fail.
DCHECK(pipe.consumer_handle.is_valid());
mojo::ScopedDataPipeConsumerHandle response_body =
std::move(pipe.consumer_handle);
std::move(found_callback_)
.Run(request_url, request_method, resource_response, std::move(ssl_info),
std::move(response_body));
data_producer_ = std::make_unique<mojo::StringDataPipeProducer>(
std::move(pipe.producer_handle));
data_producer_->Write(payload,
base::BindOnce(&SignedExchangeHandler::OnDataWritten,
base::Unretained(this)));
}
void SignedExchangeHandler::OnDataWritten(MojoResult result) {
data_producer_.reset();
std::move(finish_callback_)
.Run(network::URLLoaderCompletionStatus(
result == MOJO_RESULT_OK ? net::OK : net::ERR_FAILED));
}
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_LOADER_SIGNED_EXCHANGE_HANDLER_H_
#define CONTENT_BROWSER_LOADER_SIGNED_EXCHANGE_HANDLER_H_
#include <string>
#include "base/callback.h"
#include "base/optional.h"
#include "mojo/common/data_pipe_drainer.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/ssl/ssl_info.h"
#include "url/gurl.h"
namespace mojo {
class StringDataPipeProducer;
} // namespace mojo
namespace network {
struct ResourceResponseHead;
struct URLLoaderCompletionStatus;
} // namespace network
namespace content {
// IMPORTANT: Currenly SignedExchangeHandler doesn't implement any CBOR parsing
// logic nor verifying logic. It just behaves as if the passed body is a signed
// HTTP exchange which contains a request to "https://example.com/test.html" and
// a response with a payload which is equal to the original body.
// TODO(https://crbug.com/80374): Implement CBOR parsing logic and verifying
// logic.
class SignedExchangeHandler final
: public mojo::common::DataPipeDrainer::Client {
public:
using ExchangeFoundCallback =
base::OnceCallback<void(const GURL& request_url,
const std::string& request_method,
const network::ResourceResponseHead&,
base::Optional<net::SSLInfo>,
mojo::ScopedDataPipeConsumerHandle)>;
using ExchangeFinishedCallback =
base::OnceCallback<void(const network::URLLoaderCompletionStatus&)>;
// The passed |body| will be read to parse the signed HTTP exchange.
// TODO(https://crbug.com/80374): Consider making SignedExchangeHandler
// independent from DataPipe to make it easy to port it in //net.
explicit SignedExchangeHandler(mojo::ScopedDataPipeConsumerHandle body);
~SignedExchangeHandler() override;
// TODO(https://crbug.com/80374): Currently SignedExchangeHandler always calls
// found_callback and then calls finish_callback after reading the all buffer.
// Need to redesign this callback model when we will introduce
// SignedExchangeHandler::Reader class to read the body and introduce the
// cert verification.
void GetHTTPExchange(ExchangeFoundCallback found_callback,
ExchangeFinishedCallback finish_callback);
private:
// mojo::Common::DataPipeDrainer::Client
void OnDataAvailable(const void* data, size_t num_bytes) override;
void OnDataComplete() override;
// Called from |data_producer_|.
void OnDataWritten(MojoResult result);
mojo::ScopedDataPipeConsumerHandle body_;
std::unique_ptr<mojo::common::DataPipeDrainer> drainer_;
ExchangeFoundCallback found_callback_;
ExchangeFinishedCallback finish_callback_;
std::string original_body_string_;
std::unique_ptr<mojo::StringDataPipeProducer> data_producer_;
DISALLOW_COPY_AND_ASSIGN(SignedExchangeHandler);
};
} // namespace content
#endif // CONTENT_BROWSER_LOADER_SIGNED_EXCHANGE_HANDLER_H_
......@@ -16,7 +16,8 @@ URLLoaderRequestHandler::MaybeCreateSubresourceLoaderParams() {
bool URLLoaderRequestHandler::MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request) {
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) {
return false;
}
......
......@@ -19,6 +19,7 @@ namespace content {
class ResourceContext;
struct ResourceRequest;
struct SubresourceLoaderParams;
class ThrottlingURLLoader;
using StartLoaderCallback =
base::OnceCallback<void(network::mojom::URLLoaderRequest request,
......@@ -56,10 +57,17 @@ class CONTENT_EXPORT URLLoaderRequestHandler {
// The URLLoader interface pointer is returned in the |loader| parameter.
// The interface request for the URLLoaderClient is returned in the
// |client_request| parameter.
// The |url_loader| points to the ThrottlingURLLoader that currently controls
// the request. It can be optionally consumed to get the current
// URLLoaderClient and URLLoader so that the implementation can rebind them to
// intercept the inflight loading if necessary. Note that the |url_loader|
// will be reset after this method is called, which will also drop the
// URLLoader held by |url_loader_| if it is not unbound yet.
virtual bool MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request);
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader);
};
} // namespace content
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/web_package_loader.h"
#include "base/feature_list.h"
#include "base/strings/stringprintf.h"
#include "content/browser/loader/signed_exchange_handler.h"
#include "content/public/common/content_features.h"
#include "net/http/http_util.h"
namespace content {
namespace {
net::RedirectInfo CreateRedirectInfo(const GURL& new_url) {
net::RedirectInfo redirect_info;
redirect_info.new_url = new_url;
redirect_info.new_method = "GET";
redirect_info.status_code = 302;
redirect_info.new_site_for_cookies = redirect_info.new_url;
return redirect_info;
}
} // namespace
class WebPackageLoader::ResponseTimingInfo {
public:
explicit ResponseTimingInfo(const network::ResourceResponseHead& response)
: request_start_(response.request_start),
response_start_(response.response_start),
request_time_(response.request_time),
response_time_(response.response_time),
load_timing_(response.load_timing) {}
network::ResourceResponseHead CreateRedirectResponseHead() const {
network::ResourceResponseHead response_head;
response_head.encoded_data_length = 0;
std::string buf(base::StringPrintf("HTTP/1.1 %d %s\r\n", 302, "Found"));
response_head.headers = new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(buf.c_str(), buf.size()));
response_head.encoded_data_length = 0;
response_head.request_start = request_start_;
response_head.response_start = response_start_;
response_head.request_time = request_time_;
response_head.response_time = response_time_;
response_head.load_timing = load_timing_;
return response_head;
}
private:
const base::TimeTicks request_start_;
const base::TimeTicks response_start_;
const base::Time request_time_;
const base::Time response_time_;
const net::LoadTimingInfo load_timing_;
DISALLOW_COPY_AND_ASSIGN(ResponseTimingInfo);
};
WebPackageLoader::WebPackageLoader(
const network::ResourceResponseHead& original_response,
network::mojom::URLLoaderClientPtr forwarding_client,
network::mojom::URLLoaderClientEndpointsPtr endpoints)
: original_response_timing_info_(
base::MakeUnique<ResponseTimingInfo>(original_response)),
forwarding_client_(std::move(forwarding_client)),
url_loader_client_binding_(this),
weak_factory_(this) {
DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
url_loader_.Bind(std::move(endpoints->url_loader));
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
// We don't propagate the response to the navigation request and its
// throttles, therefore we need to call this here internally in order to
// move it forward.
// TODO(https://crbug.com/791049): Remove this when NetworkService is
// enabled by default.
url_loader_->ProceedWithResponse();
}
// Bind the endpoint with |this| to get the body DataPipe.
url_loader_client_binding_.Bind(std::move(endpoints->url_loader_client));
// |client_| will be bound with a forwarding client by ConnectToClient().
pending_client_request_ = mojo::MakeRequest(&client_);
}
WebPackageLoader::~WebPackageLoader() = default;
void WebPackageLoader::OnReceiveResponse(
const network::ResourceResponseHead& response_head,
const base::Optional<net::SSLInfo>& ssl_info,
network::mojom::DownloadedTempFilePtr downloaded_file) {
// Must not be called because this WebPackageLoader and the client endpoints
// were bound after OnReceiveResponse() is called.
NOTREACHED();
}
void WebPackageLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
// Must not be called because this WebPackageLoader and the client endpoints
// were bound after OnReceiveResponse() is called.
NOTREACHED();
}
void WebPackageLoader::OnDataDownloaded(int64_t data_len,
int64_t encoded_data_len) {
// Must not be called because this WebPackageLoader and the client endpoints
// were bound after OnReceiveResponse() is called.
NOTREACHED();
}
void WebPackageLoader::OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) {
// Must not be called because this WebPackageLoader and the client endpoints
// were bound after OnReceiveResponse() is called.
NOTREACHED();
}
void WebPackageLoader::OnReceiveCachedMetadata(
const std::vector<uint8_t>& data) {
// Curerntly CachedMetadata for WebPackage is not supported.
NOTREACHED();
}
void WebPackageLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
// TODO(https://crbug.com/80374): Implement this to progressively update the
// encoded data length in DevTools.
}
void WebPackageLoader::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
signed_exchange_handler_ =
base::MakeUnique<SignedExchangeHandler>(std::move(body));
signed_exchange_handler_->GetHTTPExchange(
base::BindOnce(&WebPackageLoader::OnHTTPExchangeFound,
weak_factory_.GetWeakPtr()),
base::BindOnce(&WebPackageLoader::OnHTTPExchangeFinished,
weak_factory_.GetWeakPtr()));
}
void WebPackageLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
// TODO(https://crbug.com/80374): Copy the data length information and pass to
// |client_| when OnHTTPExchangeFinished() is called.
}
void WebPackageLoader::FollowRedirect() {
NOTREACHED();
}
void WebPackageLoader::ProceedWithResponse() {
// TODO(https://crbug.com/791049): Remove this when NetworkService is
// enabled by default.
DCHECK(!base::FeatureList::IsEnabled(features::kNetworkService));
DCHECK(pending_body_.is_valid());
DCHECK(client_);
client_->OnStartLoadingResponseBody(std::move(pending_body_));
if (pending_completion_status_) {
client_->OnComplete(*pending_completion_status_);
}
}
void WebPackageLoader::SetPriority(net::RequestPriority priority,
int intra_priority_value) {
// TODO(https://crbug.com/80374): Implement this.
}
void WebPackageLoader::PauseReadingBodyFromNet() {
// TODO(https://crbug.com/80374): Implement this.
}
void WebPackageLoader::ResumeReadingBodyFromNet() {
// TODO(https://crbug.com/80374): Implement this.
}
void WebPackageLoader::ConnectToClient(
network::mojom::URLLoaderClientPtr client) {
DCHECK(pending_client_request_.is_pending());
mojo::FuseInterface(std::move(pending_client_request_),
client.PassInterface());
}
void WebPackageLoader::OnHTTPExchangeFound(
const GURL& request_url,
const std::string& request_method,
const network::ResourceResponseHead& resource_response,
base::Optional<net::SSLInfo> ssl_info,
mojo::ScopedDataPipeConsumerHandle body) {
// TODO(https://crbug.com/80374): Handle no-GET request_method as a error.
DCHECK(original_response_timing_info_);
forwarding_client_->OnReceiveRedirect(
CreateRedirectInfo(request_url),
std::move(original_response_timing_info_)->CreateRedirectResponseHead());
forwarding_client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
forwarding_client_.reset();
client_->OnReceiveResponse(resource_response, std::move(ssl_info),
nullptr /* downloaded_file */);
if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
// Need to wait until ProceedWithResponse() is called.
pending_body_ = std::move(body);
} else {
client_->OnStartLoadingResponseBody(std::move(body));
}
}
void WebPackageLoader::OnHTTPExchangeFinished(
const network::URLLoaderCompletionStatus& status) {
if (pending_body_.is_valid()) {
DCHECK(!base::FeatureList::IsEnabled(features::kNetworkService));
// If ProceedWithResponse() was not called yet, need to call OnComplete()
// after ProceedWithResponse() is called.
pending_completion_status_ = status;
} else {
client_->OnComplete(status);
}
}
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_LOADER_WEB_PACKAGE_LOADER_H_
#define CONTENT_BROWSER_LOADER_WEB_PACKAGE_LOADER_H_
#include "base/optional.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "net/ssl/ssl_info.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/interfaces/url_loader.mojom.h"
namespace content {
class SignedExchangeHandler;
// WebPackageLoader handles an origin-signed HTTP exchange response. It is
// created when a WebPackageRequestHandler recieves an origin-signed HTTP
// exchange response, and is owned by the handler until the StartLoaderCallback
// of WebPackageRequestHandler::StartResponse is called. After that, it is
// owned by the URLLoader mojo endpoint.
class WebPackageLoader final : public network::mojom::URLLoaderClient,
public network::mojom::URLLoader {
public:
WebPackageLoader(const network::ResourceResponseHead& original_response,
network::mojom::URLLoaderClientPtr forwarding_client,
network::mojom::URLLoaderClientEndpointsPtr endpoints);
~WebPackageLoader() override;
// network::mojom::URLLoaderClient implementation
// Only OnStartLoadingResponseBody() and OnComplete() are called.
void OnReceiveResponse(
const network::ResourceResponseHead& response_head,
const base::Optional<net::SSLInfo>& ssl_info,
network::mojom::DownloadedTempFilePtr downloaded_file) override;
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) override;
void OnDataDownloaded(int64_t data_len, int64_t encoded_data_len) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override;
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// network::mojom::URLLoader implementation
void FollowRedirect() override;
void ProceedWithResponse() override;
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
void ConnectToClient(network::mojom::URLLoaderClientPtr client);
private:
class ResponseTimingInfo;
// Called from |signed_exchange_handler_| when it finds an origin-signed HTTP
// exchange.
void OnHTTPExchangeFound(
const GURL& request_url,
const std::string& request_method,
const network::ResourceResponseHead& resource_response,
base::Optional<net::SSLInfo> ssl_info,
mojo::ScopedDataPipeConsumerHandle body);
// Called from |signed_exchange_handler_| when it finished sending the
// payload of the origin-signed HTTP response.
void OnHTTPExchangeFinished(const network::URLLoaderCompletionStatus& status);
// This timing info is used to create a dummy redirect response.
std::unique_ptr<const ResponseTimingInfo> original_response_timing_info_;
// This client is alive until OnHTTPExchangeFound() is called.
network::mojom::URLLoaderClientPtr forwarding_client_;
// This |url_loader_| is the pointer of the network URL loader.
network::mojom::URLLoaderPtr url_loader_;
// This binding connects |this| with the network URL loader.
mojo::Binding<network::mojom::URLLoaderClient> url_loader_client_binding_;
// This is pending until connected by ConnectToClient().
network::mojom::URLLoaderClientPtr client_;
// This URLLoaderClientRequest is used by ConnectToClient() to connect
// |client_|.
network::mojom::URLLoaderClientRequest pending_client_request_;
std::unique_ptr<SignedExchangeHandler> signed_exchange_handler_;
// This is used to keep the DataPipe until ProceedWithResponse() is called.
mojo::ScopedDataPipeConsumerHandle pending_body_;
// This is used to keep the status until ProceedWithResponse() is called.
base::Optional<network::URLLoaderCompletionStatus> pending_completion_status_;
base::WeakPtrFactory<WebPackageLoader> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(WebPackageLoader);
};
} // namespace content
#endif // CONTENT_BROWSER_LOADER_WEB_PACKAGE_LOADER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/web_package_request_handler.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "content/browser/loader/web_package_loader.h"
#include "content/common/throttling_url_loader.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/interfaces/url_loader.mojom.h"
namespace content {
// static
bool WebPackageRequestHandler::IsSupportedMimeType(
const std::string& mime_type) {
DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
return mime_type == "application/http-exchange+cbor";
}
WebPackageRequestHandler::WebPackageRequestHandler() : weak_factory_(this) {
DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
}
WebPackageRequestHandler::~WebPackageRequestHandler() = default;
void WebPackageRequestHandler::MaybeCreateLoader(
const network::ResourceRequest& resource_request,
ResourceContext* resource_context,
LoaderCallback callback) {
// TODO(https://crbug.com/80374): Ask WebPackageFetchManager to get the
// ongoing matching SignedExchangeHandler which was created by a
// WebPackagePrefetcher.
if (!web_package_loader_) {
std::move(callback).Run(StartLoaderCallback());
return;
}
std::move(callback).Run(base::BindOnce(
&WebPackageRequestHandler::StartResponse, weak_factory_.GetWeakPtr()));
}
bool WebPackageRequestHandler::MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) {
std::string mime_type;
if (response.was_fetched_via_service_worker || !response.headers ||
!response.headers->GetMimeType(&mime_type) ||
!IsSupportedMimeType(mime_type)) {
return false;
}
network::mojom::URLLoaderClientPtr client;
*client_request = mojo::MakeRequest(&client);
// TODO(https://crbug.com/80374): Consider creating a new ThrottlingURLLoader
// or reusing the existing ThrottlingURLLoader by reattaching URLLoaderClient,
// to support SafeBrowsing checking of the content of the WebPackage.
web_package_loader_ = base::MakeUnique<WebPackageLoader>(
response, std::move(client), url_loader->Unbind());
return true;
}
void WebPackageRequestHandler::StartResponse(
network::mojom::URLLoaderRequest request,
network::mojom::URLLoaderClientPtr client) {
web_package_loader_->ConnectToClient(std::move(client));
mojo::MakeStrongBinding(std::move(web_package_loader_), std::move(request));
}
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_LOADER_WEB_PACKAGE_REQUEST_HANDLER_H_
#define CONTENT_BROWSER_LOADER_WEB_PACKAGE_REQUEST_HANDLER_H_
#include "base/memory/weak_ptr.h"
#include "content/browser/loader/url_loader_request_handler.h"
namespace content {
class WebPackageLoader;
class WebPackageRequestHandler final : public URLLoaderRequestHandler {
public:
static bool IsSupportedMimeType(const std::string& mime_type);
WebPackageRequestHandler();
~WebPackageRequestHandler() override;
// URLLoaderRequestHandler implementation
void MaybeCreateLoader(const network::ResourceRequest& resource_request,
ResourceContext* resource_context,
LoaderCallback callback) override;
bool MaybeCreateLoaderForResponse(
const network::ResourceResponseHead& response,
network::mojom::URLLoaderPtr* loader,
network::mojom::URLLoaderClientRequest* client_request,
ThrottlingURLLoader* url_loader) override;
private:
void StartResponse(network::mojom::URLLoaderRequest request,
network::mojom::URLLoaderClientPtr client);
// Valid after MaybeCreateLoaderForResponse intercepts the request and until
// the loader is re-bound to the new client for the redirected request in
// StartResponse.
std::unique_ptr<WebPackageLoader> web_package_loader_;
base::WeakPtrFactory<WebPackageRequestHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(WebPackageRequestHandler);
};
} // namespace content
#endif // CONTENT_BROWSER_LOADER_WEB_PACKAGE_REQUEST_HANDLER_H_
......@@ -22,7 +22,8 @@ bool IsNavigationMojoResponseEnabled() {
if (!IsBrowserSideNavigationEnabled())
return false;
return base::FeatureList::IsEnabled(features::kNavigationMojoResponse);
return base::FeatureList::IsEnabled(features::kNavigationMojoResponse) ||
base::FeatureList::IsEnabled(features::kSignedHTTPExchange);
}
} // namespace content
......@@ -96,6 +96,8 @@ crbug.com/805049 virtual/spv175/paint/invalidation/scroll/fixed-scroll-simple.ht
crbug.com/417782 virtual/spv175/compositing/overflow/border-radius-composited-subframe.html [ Failure ]
crbug.com/417782 virtual/spv175/paint/invalidation/window-resize/window-resize-vertical-writing-mode.html [ Crash ]
crbug.com/803774 http/tests/loading/htxg/ [ Skip ]
########## Ref tests can't be rebaselined ##########
crbug.com/504613 crbug.com/524248 [ Mac ] paint/images/image-backgrounds-not-antialiased.html [ Failure ]
......
......@@ -643,5 +643,15 @@
"prefix": "unified-autoplay",
"base": "external/wpt/feature-policy",
"args": ["--autoplay-policy=document-user-activation-required"]
},
{
"prefix": "htxg",
"base": "http/tests/loading/htxg/",
"args": ["--enable-features=SignedHTTPExchange"]
},
{
"prefix": "htxg-with-network-service",
"base": "http/tests/loading/htxg/",
"args": ["--enable-features=SignedHTTPExchange,NetworkService"]
}
]
<!DOCTYPE html>
<title>Location of origin-signed HTTP response</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/get-host-info.js?pipe=sub"></script>
<body>
<script>
function with_iframe(url) {
return new Promise(function(resolve) {
const frame = document.createElement('iframe');
frame.src = url;
frame.onload = function() { resolve(frame); };
document.body.appendChild(frame);
});
}
promise_test(function(t) {
const url = 'resources/origin-signed-response-iframe.php';
return with_iframe(url)
.then((frame) => {
var channel = new MessageChannel();
var promise =
new Promise((resolve) => { channel.port1.onmessage = resolve; });
frame.contentWindow.postMessage(
{port: channel.port2}, '*', [channel.port2]);
return promise;
})
.then((event) => {
// TODO(https://crbug.com/80374): Currently this URL is hard coded in
// SignedExchangeHandler.
assert_equals(event.data.location, 'https://example.com/test.html');
});
}, 'Location of origin-signed HTTP response');
</script>
</body>
<?php
header('Content-Type: application/http-exchange+cbor');
?>
<!DOCTYPE html>
<body>
<script>
window.addEventListener('message', (event) => {
event.data.port.postMessage({location: document.location.href});
}, false);
</script>
hello<br>
world
</body>
This directory is for testing the SignedHTTPExchange feature with the Network
Service.
This directory is for testing the SignedHTTPExchange feature.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment