// 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/loader/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/loader/result_codes.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(base::WeakPtr client) : client_(std::move(client)) {} UrlLoader::~UrlLoader() = default; // Modeled on `content::PepperURLLoaderHost::OnHostMsgOpen()`. void UrlLoader::Open(const UrlRequest& request, base::OnceCallback 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 = true; ignore_redirects_ = request.ignore_redirects; blink_loader_ = client_->CreateAssociatedURLLoader(options); blink_loader_->LoadAsynchronously(blink_request, this); } // Modeled on `ppapi::proxy::URLLoaderResource::ReadResponseBody()`. void UrlLoader::ReadResponseBody(base::span buffer, base::OnceCallback 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(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 UrlLoader::Close() { if (state_ != LoadingState::kLoadComplete) AbortLoad(Result::kErrorAborted); } // Modeled on `content::PepperURLLoaderHost::WillFollowRedirect()`. bool UrlLoader::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 UrlLoader::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 UrlLoader::DidReceiveResponse(const blink::WebURLResponse& response) { DCHECK_EQ(state_, LoadingState::kOpening); // Modeled on `content::DataFromWebURLResponse()`. response_.status_code = response.HttpStatusCode(); HeadersToString headers_to_string(response_.headers); response.VisitHttpHeaderFields(&headers_to_string); state_ = LoadingState::kStreamingData; std::move(open_callback_).Run(Result::kSuccess); } void UrlLoader::DidDownloadData(uint64_t data_length) { // Doesn't apply to PDF viewer requests. NOTREACHED(); } // Modeled on `content::PepperURLLoaderHost::DidReceiveData()`. void UrlLoader::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 UrlLoader::DidFinishLoading() { DCHECK_EQ(state_, LoadingState::kStreamingData); SetLoadComplete(Result::kSuccess); RunReadCallback(); } // Modeled on `content::PepperURLLoaderHost::DidFail()`. void UrlLoader::DidFail(const blink::WebURLError& error) { DCHECK(state_ == LoadingState::kOpening || state_ == LoadingState::kStreamingData) << static_cast(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 UrlLoader::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 UrlLoader::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(Result::kSuccess == 0, "Result::kSuccess should be equivalent to 0 bytes"); } client_buffer_ = {}; std::move(read_callback_).Run(num_bytes); } void UrlLoader::SetLoadComplete(int32_t result) { DCHECK_NE(state_, LoadingState::kLoadComplete); DCHECK_LE(result, 0); state_ = LoadingState::kLoadComplete; complete_result_ = result; blink_loader_.reset(); } } // namespace chrome_pdf