| // 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 |