blob: bc87d88de36572a582308de39bb10c12e81e7b8d [file] [log] [blame]
// Copyright 2024 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/devtools/request_body_collector.h"
#include "base/containers/extend.h"
#include "base/memory/raw_ref.h"
#include "base/numerics/safe_conversions.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
namespace content {
class RequestBodyCollector::BodyReader : public mojo::DataPipeDrainer::Client {
public:
BodyReader(
RequestBodyCollector& collector,
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter)
: collector_(collector), data_pipe_getter_(std::move(data_pipe_getter)) {
data_pipe_getter_.set_disconnect_handler(
base::BindOnce(&BodyReader::OnFailure, base::Unretained(this)));
mojo::ScopedDataPipeProducerHandle pipe_producer;
MojoResult result =
CreateDataPipe(/*options=*/nullptr, pipe_producer, pipe_consumer_);
CHECK_EQ(MOJO_RESULT_OK, result);
data_pipe_getter_->Read(
std::move(pipe_producer),
base::BindOnce(&BodyReader::OnReadStarted, base::Unretained(this)));
}
~BodyReader() override = default;
private:
// mojo::DataPipeDrainer::Client overrides
void OnDataAvailable(base::span<const uint8_t> data) override {
CHECK_NE(expected_size_, 0ul);
base::Extend(bytes_, data);
}
void OnDataComplete() override {
BodyEntry entry = expected_size_ == bytes_.size()
? BodyEntry(std::move(bytes_))
: base::unexpected("Unexpected end of data");
collector_->OnReaderComplete(this, std::move(entry));
}
void OnFailure() {
collector_->OnReaderComplete(this, base::unexpected("Error reading blob"));
// `this` is invalid at this point.
}
void OnReadStarted(int32_t status, uint64_t size) {
if (status != net::OK) {
OnFailure();
// `this` is invalid at this point.
return;
}
expected_size_ = base::checked_cast<size_t>(size);
bytes_.reserve(expected_size_);
pipe_drainer_.emplace(this, std::move(pipe_consumer_));
}
const raw_ref<RequestBodyCollector> collector_;
mojo::Remote<network::mojom::DataPipeGetter> data_pipe_getter_;
size_t expected_size_ = 0;
mojo::ScopedDataPipeConsumerHandle pipe_consumer_;
std::optional<mojo::DataPipeDrainer> pipe_drainer_;
std::vector<uint8_t> bytes_;
};
// static
std::unique_ptr<RequestBodyCollector> RequestBodyCollector::Collect(
const network::ResourceRequestBody& request_body,
CompletionCallback callback) {
std::vector<BodyEntry> bodies;
const auto& elements = *request_body.elements();
bodies.resize(elements.size());
ReadersMap readers;
std::unique_ptr<RequestBodyCollector> collector(new RequestBodyCollector());
for (size_t i = 0; i < elements.size(); ++i) {
const network::DataElement& element = elements[i];
switch (element.type()) {
case network::DataElement::Tag::kBytes:
bodies[i] = element.As<network::DataElementBytes>().bytes();
break;
case network::DataElement::Tag::kDataPipe: {
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter =
element.As<network::DataElementDataPipe>().CloneDataPipeGetter();
readers.insert(
std::make_pair(std::make_unique<BodyReader>(
*collector, std::move(data_pipe_getter)),
i));
break;
}
case network::DataElement::Tag::kFile:
case network::DataElement::Tag::kChunkedDataPipe:
bodies[i] = base::unexpected("Unsupported entry");
break;
}
}
if (readers.empty()) {
std::move(callback).Run(std::move(bodies));
return nullptr;
}
collector->callback_ = std::move(callback);
collector->bodies_ = std::move(bodies);
collector->readers_ = std::move(readers);
return collector;
}
RequestBodyCollector::~RequestBodyCollector() = default;
RequestBodyCollector::RequestBodyCollector() = default;
void RequestBodyCollector::OnReaderComplete(BodyReader* reader,
BodyEntry entry) {
auto it = readers_.find(reader);
CHECK(it != readers_.end());
bodies_[it->second] = std::move(entry);
readers_.erase(it);
if (!readers_.empty()) {
return;
}
std::move(callback_).Run(bodies_);
// `this` may be invalid at this point due to callback invocation above.
}
} // namespace content