blob: 7e79e74dc1b3fc09b273176494af8eb92eff44c8 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/content_decoding_util.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/raw_span.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/filter/filter_source_stream.h"
#include "net/filter/source_stream.h"
#include "services/network/public/cpp/data_buffer_factory.h"
namespace network {
namespace {
// An in-memory, synchronous net::SourceStream that reads from a base::span.
class SpanSourceStream : public net::SourceStream {
public:
explicit SpanSourceStream(base::span<const uint8_t> data)
: net::SourceStream(net::SourceStreamType::kNone), data_(data) {}
~SpanSourceStream() override = default;
int Read(net::IOBuffer* dest_buffer,
int buffer_size,
net::CompletionOnceCallback callback) override {
CHECK(dest_buffer);
CHECK_GE(buffer_size, 0);
if (data_.empty()) {
return 0;
}
size_t bytes_to_copy =
std::min(data_.size(), static_cast<size_t>(buffer_size));
dest_buffer->span()
.first(bytes_to_copy)
.copy_from(data_.first(bytes_to_copy));
data_ = data_.subspan(bytes_to_copy);
return static_cast<int>(bytes_to_copy);
}
std::string Description() const override { return "SpanSourceStream"; }
bool MayHaveMoreBytes() const override { return !data_.empty(); }
private:
base::raw_span<const uint8_t> data_;
};
} // namespace
// static
std::unique_ptr<network::DataBufferList> ContentDecodingUtil::Decode(
base::span<const uint8_t> upstream_data,
const std::vector<net::SourceStreamType>& types,
network::DataBufferFactory& data_buffer_factory,
uint32_t buffer_size) {
TRACE_EVENT("net", "ContentDecodingUtil::Decode");
CHECK_GT(buffer_size, 0u);
if (!base::IsValueInRangeForNumericType<int>(upstream_data.size())) {
return nullptr;
}
const int read_buffer_size = base::checked_cast<int>(buffer_size);
// Wrap the input in-memory data in a synchronous SourceStream.
auto source_stream = std::make_unique<SpanSourceStream>(upstream_data);
// Create the chained decoding stream for the specified content encodings.
std::unique_ptr<net::SourceStream> decoding_stream =
net::FilterSourceStream::CreateDecodingSourceStream(
std::move(source_stream), types);
if (!decoding_stream) {
return nullptr;
}
auto buffers = data_buffer_factory.CreateDataBufferList();
// Read and decode data in chunks until EOF.
while (true) {
auto buffer = data_buffer_factory.AllocateDataBuffer(buffer_size);
auto io_buffer = base::MakeRefCounted<net::WrappedIOBuffer>(buffer->data());
// SpanSourceStream completes reads synchronously, so the callback is
// never invoked.
int result =
decoding_stream->Read(io_buffer.get(), read_buffer_size,
base::BindOnce([](int) { NOTREACHED(); }));
if (result < 0) {
if (result == net::ERR_IO_PENDING) {
// Since SpanSourceStream is synchronous, FilterSourceStream should also
// return synchronously.
NOTREACHED();
}
// Reset `decoding_stream` before `buffer` is destroyed to avoid a
// dangling pointer in FilterSourceStream's internal IOBuffer reference to
// `buffer->data()`.
decoding_stream.reset();
return nullptr;
}
if (result == 0) {
// Reached EOF.
// Reset `decoding_stream` before `buffer` is destroyed so that the
// FilterSourceStream releases its reference to `io_buffer` before
// `buffer` goes out of scope and frees its memory.
decoding_stream.reset();
break;
}
// Shrink buffer to actual decoded bytes read if necessary.
if (buffer->data().size() > static_cast<size_t>(result)) {
buffer->Shrink(result);
}
buffers->Append(std::move(buffer));
}
return buffers;
}
} // namespace network