blob: 6d2d89ec228e4101825c7a426e1a6f5ad9bfdf23 [file] [log] [blame]
// Copyright 2020 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 "pdf/ppapi_migration/url_loader.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/check_op.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "net/base/net_errors.h"
#include "net/cookies/site_for_cookies.h"
#include "net/http/http_util.h"
#include "pdf/ppapi_migration/callback.h"
#include "pdf/ppapi_migration/result_codes.h"
#include "ppapi/c/trusted/ppb_url_loader_trusted.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/instance_handle.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/url_loader.h"
#include "ppapi/cpp/url_request_info.h"
#include "ppapi/cpp/url_response_info.h"
#include "ppapi/cpp/var.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/public/platform/web_http_body.h"
#include "third_party/blink/public/platform/web_http_header_visitor.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_associated_url_loader.h"
#include "third_party/blink/public/web/web_associated_url_loader_options.h"
#include "url/gurl.h"
namespace chrome_pdf {
namespace {
// Taken from `content/renderer/pepper/url_response_info_util.cc`.
class HeadersToString final : public blink::WebHTTPHeaderVisitor {
public:
explicit HeadersToString(std::string& buffer_ref) : buffer_ref_(buffer_ref) {}
void VisitHeader(const blink::WebString& name,
const blink::WebString& value) override {
if (!buffer_ref_.empty())
buffer_ref_.append("\n");
buffer_ref_.append(name.Utf8());
buffer_ref_.append(": ");
buffer_ref_.append(value.Utf8());
}
private:
// Reference allows writing directly into `UrlResponse::headers`.
std::string& buffer_ref_;
};
} // namespace
UrlRequest::UrlRequest() = default;
UrlRequest::UrlRequest(const UrlRequest& other) = default;
UrlRequest::UrlRequest(UrlRequest&& other) noexcept = default;
UrlRequest& UrlRequest::operator=(const UrlRequest& other) = default;
UrlRequest& UrlRequest::operator=(UrlRequest&& other) noexcept = default;
UrlRequest::~UrlRequest() = default;
UrlResponse::UrlResponse() = default;
UrlResponse::UrlResponse(const UrlResponse& other) = default;
UrlResponse::UrlResponse(UrlResponse&& other) noexcept = default;
UrlResponse& UrlResponse::operator=(const UrlResponse& other) = default;
UrlResponse& UrlResponse::operator=(UrlResponse&& other) noexcept = default;
UrlResponse::~UrlResponse() = default;
UrlLoader::UrlLoader() = default;
UrlLoader::~UrlLoader() = default;
BlinkUrlLoader::BlinkUrlLoader(base::WeakPtr<Client> client)
: client_(std::move(client)) {}
BlinkUrlLoader::~BlinkUrlLoader() = default;
// Modeled on `content::PepperURLLoaderHost::OnHostMsgGrantUniversalAccess()`.
void BlinkUrlLoader::GrantUniversalAccess() {
DCHECK_EQ(state_, LoadingState::kWaitingToOpen);
grant_universal_access_ = true;
}
// Modeled on `content::PepperURLLoaderHost::OnHostMsgOpen()`.
void BlinkUrlLoader::Open(const UrlRequest& request, ResultCallback callback) {
DCHECK_EQ(state_, LoadingState::kWaitingToOpen);
DCHECK(callback);
state_ = LoadingState::kOpening;
open_callback_ = std::move(callback);
if (!client_ || !client_->IsValid()) {
AbortLoad(Result::kErrorFailed);
return;
}
// Modeled on `content::CreateWebURLRequest()`.
// TODO(crbug.com/1129291): The original code performs additional validations
// that we probably don't need in the new process model.
blink::WebURLRequest blink_request;
blink_request.SetUrl(
client_->CompleteURL(blink::WebString::FromUTF8(request.url)));
blink_request.SetHttpMethod(blink::WebString::FromASCII(request.method));
blink_request.SetSiteForCookies(client_->SiteForCookies());
blink_request.SetSkipServiceWorker(true);
// Note: The PDF plugin doesn't set the `X-Requested-With` header.
if (!request.headers.empty()) {
net::HttpUtil::HeadersIterator it(request.headers.begin(),
request.headers.end(), "\n\r");
while (it.GetNext()) {
blink_request.AddHttpHeaderField(blink::WebString::FromUTF8(it.name()),
blink::WebString::FromUTF8(it.values()));
}
}
if (!request.body.empty()) {
blink::WebHTTPBody body;
body.Initialize();
body.AppendData(request.body);
blink_request.SetHttpBody(body);
}
if (!request.custom_referrer_url.empty()) {
client_->SetReferrerForRequest(blink_request,
GURL(request.custom_referrer_url));
}
buffer_lower_threshold_ = request.buffer_lower_threshold;
buffer_upper_threshold_ = request.buffer_upper_threshold;
DCHECK_GT(buffer_lower_threshold_, 0u);
DCHECK_LE(buffer_lower_threshold_, buffer_upper_threshold_);
blink_request.SetRequestContext(blink::mojom::RequestContextType::PLUGIN);
blink_request.SetRequestDestination(
network::mojom::RequestDestination::kEmbed);
// TODO(crbug.com/822081): Revisit whether we need universal access.
blink::WebAssociatedURLLoaderOptions options;
options.grant_universal_access = grant_universal_access_;
ignore_redirects_ = request.ignore_redirects;
blink_loader_ = client_->CreateAssociatedURLLoader(options);
blink_loader_->LoadAsynchronously(blink_request, this);
}
// Modeled on `ppapi::proxy::URLLoaderResource::ReadResponseBody()`.
void BlinkUrlLoader::ReadResponseBody(base::span<char> buffer,
ResultCallback callback) {
// Can be in `kLoadComplete` if still reading after loading finished.
DCHECK(state_ == LoadingState::kStreamingData ||
state_ == LoadingState::kLoadComplete)
<< static_cast<int>(state_);
if (buffer.empty()) {
std::move(callback).Run(Result::kErrorBadArgument);
return;
}
DCHECK(!read_callback_);
DCHECK(callback);
read_callback_ = std::move(callback);
client_buffer_ = buffer;
if (!buffer_.empty() || state_ == LoadingState::kLoadComplete)
RunReadCallback();
}
// Modeled on `ppapi::proxy::URLLoadResource::Close()`.
void BlinkUrlLoader::Close() {
if (state_ != LoadingState::kLoadComplete)
AbortLoad(Result::kErrorAborted);
}
// Modeled on `content::PepperURLLoaderHost::WillFollowRedirect()`.
bool BlinkUrlLoader::WillFollowRedirect(
const blink::WebURL& new_url,
const blink::WebURLResponse& redirect_response) {
DCHECK_EQ(state_, LoadingState::kOpening);
// TODO(crbug.com/1129291): The original code performs additional validations
// that we probably don't need in the new process model.
// Note that `pp::URLLoader::FollowRedirect()` is not supported, so the
// redirect can be canceled immediately by returning `false` here.
return !ignore_redirects_;
}
void BlinkUrlLoader::DidSendData(uint64_t bytes_sent,
uint64_t total_bytes_to_be_sent) {
// Doesn't apply to PDF viewer requests.
NOTREACHED();
}
// Modeled on `content::PepperURLLoaderHost::DidReceiveResponse()`.
void BlinkUrlLoader::DidReceiveResponse(const blink::WebURLResponse& response) {
DCHECK_EQ(state_, LoadingState::kOpening);
// Modeled on `content::DataFromWebURLResponse()`.
mutable_response().status_code = response.HttpStatusCode();
HeadersToString headers_to_string(mutable_response().headers);
response.VisitHttpHeaderFields(&headers_to_string);
state_ = LoadingState::kStreamingData;
std::move(open_callback_).Run(Result::kSuccess);
}
void BlinkUrlLoader::DidDownloadData(uint64_t data_length) {
// Doesn't apply to PDF viewer requests.
NOTREACHED();
}
// Modeled on `content::PepperURLLoaderHost::DidReceiveData()`.
void BlinkUrlLoader::DidReceiveData(const char* data, int data_length) {
DCHECK_EQ(state_, LoadingState::kStreamingData);
// It's surprisingly difficult to guarantee that this is always >0.
if (data_length < 1)
return;
buffer_.insert(buffer_.end(), data, data + data_length);
// Defer loading if the buffer is too full.
if (!deferring_loading_ && buffer_.size() >= buffer_upper_threshold_) {
deferring_loading_ = true;
blink_loader_->SetDefersLoading(true);
}
RunReadCallback();
}
// Modeled on `content::PepperURLLoaderHost::DidFinishLoading()`.
void BlinkUrlLoader::DidFinishLoading() {
DCHECK_EQ(state_, LoadingState::kStreamingData);
SetLoadComplete(Result::kSuccess);
RunReadCallback();
}
// Modeled on `content::PepperURLLoaderHost::DidFail()`.
void BlinkUrlLoader::DidFail(const blink::WebURLError& error) {
DCHECK(state_ == LoadingState::kOpening ||
state_ == LoadingState::kStreamingData)
<< static_cast<int>(state_);
int32_t pp_error = Result::kErrorFailed;
switch (error.reason()) {
case net::ERR_ACCESS_DENIED:
case net::ERR_NETWORK_ACCESS_DENIED:
pp_error = Result::kErrorNoAccess;
break;
default:
if (error.is_web_security_violation())
pp_error = Result::kErrorNoAccess;
break;
}
AbortLoad(pp_error);
}
void BlinkUrlLoader::AbortLoad(int32_t result) {
DCHECK_LT(result, 0);
SetLoadComplete(result);
buffer_.clear();
if (open_callback_) {
DCHECK(!read_callback_);
std::move(open_callback_).Run(complete_result_);
} else if (read_callback_) {
RunReadCallback();
}
}
// Modeled on `ppapi::proxy::URLLoaderResource::FillUserBuffer()`.
void BlinkUrlLoader::RunReadCallback() {
if (!read_callback_)
return;
DCHECK(!client_buffer_.empty());
int32_t num_bytes = std::min(
{buffer_.size(), client_buffer_.size(), static_cast<size_t>(INT32_MAX)});
if (num_bytes > 0) {
auto read_begin = buffer_.begin();
auto read_end = read_begin + num_bytes;
std::copy(read_begin, read_end, client_buffer_.data());
buffer_.erase(read_begin, read_end);
// Resume loading if the buffer is too empty.
if (deferring_loading_ && buffer_.size() <= buffer_lower_threshold_) {
deferring_loading_ = false;
blink_loader_->SetDefersLoading(false);
}
} else {
DCHECK_EQ(state_, LoadingState::kLoadComplete);
num_bytes = complete_result_;
DCHECK_LE(num_bytes, 0);
static_assert(Result::kSuccess == 0,
"Result::kSuccess should be equivalent to 0 bytes");
}
client_buffer_ = {};
std::move(read_callback_).Run(num_bytes);
}
void BlinkUrlLoader::SetLoadComplete(int32_t result) {
DCHECK_NE(state_, LoadingState::kLoadComplete);
DCHECK_LE(result, 0);
state_ = LoadingState::kLoadComplete;
complete_result_ = result;
blink_loader_.reset();
}
PepperUrlLoader::PepperUrlLoader(pp::InstanceHandle plugin_instance)
: plugin_instance_(plugin_instance), pepper_loader_(plugin_instance) {}
PepperUrlLoader::~PepperUrlLoader() = default;
void PepperUrlLoader::GrantUniversalAccess() {
const PPB_URLLoaderTrusted* trusted_interface =
static_cast<const PPB_URLLoaderTrusted*>(
pp::Module::Get()->GetBrowserInterface(
PPB_URLLOADERTRUSTED_INTERFACE));
if (trusted_interface)
trusted_interface->GrantUniversalAccess(pepper_loader_.pp_resource());
}
void PepperUrlLoader::Open(const UrlRequest& request, ResultCallback callback) {
pp::URLRequestInfo pp_request(plugin_instance_);
pp_request.SetURL(request.url);
pp_request.SetMethod(request.method);
pp_request.SetCustomReferrerURL(request.url);
if (request.ignore_redirects)
pp_request.SetFollowRedirects(false);
if (!request.custom_referrer_url.empty())
pp_request.SetCustomReferrerURL(request.custom_referrer_url);
if (!request.headers.empty())
pp_request.SetHeaders(request.headers);
if (!request.body.empty())
pp_request.AppendDataToBody(request.body.data(), request.body.size());
pp::CompletionCallback pp_callback = PPCompletionCallbackFromResultCallback(
base::BindOnce(&PepperUrlLoader::DidOpen, weak_factory_.GetWeakPtr(),
std::move(callback)));
int32_t result = pepper_loader_.Open(pp_request, pp_callback);
if (result != PP_OK_COMPLETIONPENDING)
pp_callback.Run(result);
}
void PepperUrlLoader::ReadResponseBody(base::span<char> buffer,
ResultCallback callback) {
pp::CompletionCallback pp_callback =
PPCompletionCallbackFromResultCallback(std::move(callback));
int32_t result = pepper_loader_.ReadResponseBody(buffer.data(), buffer.size(),
pp_callback);
if (result != PP_OK_COMPLETIONPENDING)
pp_callback.Run(result);
}
void PepperUrlLoader::Close() {
pepper_loader_.Close();
}
void PepperUrlLoader::DidOpen(ResultCallback callback, int32_t result) {
pp::URLResponseInfo pp_response = pepper_loader_.GetResponseInfo();
mutable_response().status_code = pp_response.GetStatusCode();
pp::Var headers_var = pp_response.GetHeaders();
if (headers_var.is_string()) {
mutable_response().headers = headers_var.AsString();
} else {
mutable_response().headers.clear();
}
std::move(callback).Run(result);
}
} // namespace chrome_pdf