910e62b5创建于 1月15日历史提交
// 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