blob: 3aa3d7b1d10bc2d826cd471f5010b378ed797469 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/cross_origin_read_blocking_checker.h"
#include <string_view>
#include "base/functional/callback.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_sniffer.h"
#include "services/network/public/cpp/orb/orb_api.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_reader.h"
#include "url/origin.h"
namespace content {
// The CrossOriginReadBlockingChecker lives on the UI thread, but blobs must be
// read on IO. This class handles all blob access for
// CrossOriginReadBlockingChecker.
class CrossOriginReadBlockingChecker::BlobIOState {
public:
BlobIOState(base::WeakPtr<CrossOriginReadBlockingChecker> checker,
std::unique_ptr<storage::BlobDataHandle> blob_data_handle)
: checker_(std::move(checker)),
blob_data_handle_(std::move(blob_data_handle)) {}
~BlobIOState() { DCHECK_CURRENTLY_ON(BrowserThread::IO); }
void StartSniffing() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
blob_reader_ = blob_data_handle_->CreateReader();
const storage::BlobReader::Status size_status = blob_reader_->CalculateSize(
base::BindOnce(&BlobIOState::DidCalculateSize, base::Unretained(this)));
switch (size_status) {
case storage::BlobReader::Status::NET_ERROR:
OnNetError();
return;
case storage::BlobReader::Status::IO_PENDING:
return;
case storage::BlobReader::Status::DONE:
DidCalculateSize(net::OK);
return;
}
}
private:
void DidCalculateSize(int result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
size_t buf_size = net::kMaxBytesToSniff;
if (buf_size > blob_reader_->total_size()) {
buf_size = blob_reader_->total_size();
}
buffer_ = base::MakeRefCounted<net::IOBufferWithSize>(buf_size);
int bytes_read;
const storage::BlobReader::Status status = blob_reader_->Read(
buffer_.get(), buf_size, &bytes_read,
base::BindOnce(&BlobIOState::OnReadComplete, base::Unretained(this)));
switch (status) {
case storage::BlobReader::Status::NET_ERROR:
OnNetError();
return;
case storage::BlobReader::Status::IO_PENDING:
return;
case storage::BlobReader::Status::DONE:
OnReadComplete(bytes_read);
return;
}
}
void OnNetError() {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CrossOriginReadBlockingChecker::OnNetError,
checker_, blob_reader_->net_error()));
}
void OnReadComplete(int bytes_read) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&CrossOriginReadBlockingChecker::OnReadComplete,
checker_, bytes_read, buffer_,
blob_reader_->net_error()));
}
// |checker_| should only be accessed on the thread the navigation loader is
// running on.
base::WeakPtr<CrossOriginReadBlockingChecker> checker_;
scoped_refptr<net::IOBufferWithSize> buffer_;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle_;
std::unique_ptr<storage::BlobReader> blob_reader_;
};
CrossOriginReadBlockingChecker::CrossOriginReadBlockingChecker(
const network::ResourceRequest& request,
const network::mojom::URLResponseHead& response,
const storage::BlobDataHandle& blob_data_handle,
network::orb::PerFactoryState* orb_state,
base::OnceCallback<void(Result)> callback)
: callback_(std::move(callback)) {
DCHECK(!callback_.is_null());
orb_analyzer_ = network::orb::ResponseAnalyzer::Create(orb_state);
auto decision =
orb_analyzer_->Init(request.url, request.request_initiator, request.mode,
request.destination, response);
switch (decision) {
case network::orb::ResponseAnalyzer::Decision::kBlock:
OnBlocked();
return;
case network::orb::ResponseAnalyzer::Decision::kAllow:
OnAllowed();
return;
case network::orb::ResponseAnalyzer::Decision::kSniffMore:
blob_io_state_ = std::make_unique<BlobIOState>(
weak_factory_.GetWeakPtr(),
std::make_unique<storage::BlobDataHandle>(blob_data_handle));
// base::Unretained is safe because |blob_io_state_| will be deleted on
// the IO thread.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&BlobIOState::StartSniffing,
base::Unretained(blob_io_state_.get())));
return;
}
NOTREACHED(); // Unrecognized `decision` value?
}
CrossOriginReadBlockingChecker::~CrossOriginReadBlockingChecker() {
GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, std::move(blob_io_state_));
}
int CrossOriginReadBlockingChecker::GetNetError() {
return net_error_;
}
void CrossOriginReadBlockingChecker::OnAllowed() {
std::move(callback_).Run(Result::kAllowed);
}
void CrossOriginReadBlockingChecker::OnBlocked() {
std::move(callback_).Run(orb_analyzer_->ShouldReportBlockedResponse()
? Result::kBlocked_ShouldReport
: Result::kBlocked_ShouldNotReport);
}
void CrossOriginReadBlockingChecker::OnNetError(int net_error) {
net_error_ = net_error;
std::move(callback_).Run(Result::kNetError);
}
void CrossOriginReadBlockingChecker::OnReadComplete(
int bytes_read,
scoped_refptr<net::IOBufferWithSize> buffer,
int net_error) {
if (bytes_read != buffer->size()) {
OnNetError(net_error);
return;
}
std::string_view data(buffer->data(), bytes_read);
network::orb::ResponseAnalyzer::Decision orb_decision =
orb_analyzer_->Sniff(data);
// At OnReadComplete we are out of data, so fall back to
// HandleEndOfSniffableResponseBody if no allow/block `orb_decision` has been
// reached yet.
if (orb_decision == network::orb::ResponseAnalyzer::Decision::kSniffMore) {
orb_decision = orb_analyzer_->HandleEndOfSniffableResponseBody();
DCHECK_NE(network::orb::ResponseAnalyzer::Decision::kSniffMore,
orb_decision);
}
switch (orb_decision) {
case network::orb::ResponseAnalyzer::Decision::kBlock:
OnBlocked();
return;
case network::orb::ResponseAnalyzer::Decision::kAllow:
OnAllowed();
return;
case network::orb::ResponseAnalyzer::Decision::kSniffMore:
// This should be impossible after going through
// HandleEndOfSniffableResponseBody above.
NOTREACHED();
}
NOTREACHED();
}
} // namespace content