// 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 #include #include #include #include #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 "ppapi/c/pp_errors.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_(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(PP_ERROR_FAILED); 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 buffer, ResultCallback callback) { // Can be in `kLoadComplete` if still reading after loading finished. DCHECK(state_ == LoadingState::kStreamingData || state_ == LoadingState::kLoadComplete) << static_cast(state_); if (buffer.empty()) { std::move(callback).Run(PP_ERROR_BADARGUMENT); 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(PP_ERROR_ABORTED); } // 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(PP_OK); } 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(PP_OK); RunReadCallback(); } // Modeled on `content::PepperURLLoaderHost::DidFail()`. void BlinkUrlLoader::DidFail(const blink::WebURLError& error) { DCHECK(state_ == LoadingState::kOpening || state_ == LoadingState::kStreamingData) << static_cast(state_); int32_t pp_error = PP_ERROR_FAILED; switch (error.reason()) { case net::ERR_ACCESS_DENIED: case net::ERR_NETWORK_ACCESS_DENIED: pp_error = PP_ERROR_NOACCESS; break; default: if (error.is_web_security_violation()) pp_error = PP_ERROR_NOACCESS; 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(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(PP_OK == 0, "PP_OK 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( 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); 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 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