From e8eaa80ce2959ab9eb73009c754ba462752ed667 Mon Sep 17 00:00:00 2001 From: Hodlinator <172445034+hodlinator@users.noreply.github.com> Date: Tue, 28 Jul 2026 09:49:31 +0200 Subject: [PATCH 1/3] util: LineReader - Don't include newline and acknowledge single-char \r Due to the preceeding if-condition we know \n is always there. --- src/util/string.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/util/string.cpp b/src/util/string.cpp index a203bc1b19c..8962513d511 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -36,9 +36,10 @@ std::optional LineReader::ReadLine() // If the character we just consumed was \n, the line is terminated. // The \n itself does not count against max_line_length. if (c == '\n') { - const std::string_view untrimmed_line(reinterpret_cast(std::to_address(line_start)), count); - std::string_view line = RemoveSuffixView(untrimmed_line, "\n"); - return RemoveSuffixView(line, "\r"); + std::string_view line{reinterpret_cast(std::to_address(line_start)), count - 1}; + if (!line.empty() && line.back() == '\r') + line.remove_suffix(1); + return line; } // If the character we just consumed gives us a line length greater // than max_line_length, and we are not at the end of the line (or buffer) yet, From 5d5cdcd79d4c506113617e94cfc6f7c29d0a90b2 Mon Sep 17 00:00:00 2001 From: Hodlinator <172445034+hodlinator@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:02:20 +0200 Subject: [PATCH 2/3] util: Make LineReader consistently use string_views Forcing the input through std::byte while always outputting strings was cumbersome. Also changes LineReader to a class and makes the fields private. --- src/torcontrol.cpp | 3 +-- src/util/string.cpp | 34 ++++++++++++++++------------------ src/util/string.h | 16 +++++++++------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index f7749d4a1ed..9e00c7151ba 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -179,7 +179,6 @@ bool TorControlConnection::ReceiveAndProcess() bool TorControlConnection::ProcessBuffer() { util::LineReader reader(m_recv_buffer, MAX_LINE_LENGTH); - auto start = reader.it; while (auto line = reader.ReadLine()) { if (m_message.lines.size() == MAX_LINE_COUNT) { @@ -210,7 +209,7 @@ bool TorControlConnection::ProcessBuffer() } } - m_recv_buffer.erase(m_recv_buffer.begin(), m_recv_buffer.begin() + std::distance(start, reader.it)); + m_recv_buffer.erase(m_recv_buffer.begin(), m_recv_buffer.begin() + reader.Consumed()); return true; } diff --git a/src/util/string.cpp b/src/util/string.cpp index 8962513d511..d9d59ef5717 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -17,26 +17,24 @@ void ReplaceAll(std::string& in_out, const std::string& search, const std::strin in_out = std::regex_replace(in_out, std::regex(search), substitute); } -LineReader::LineReader(std::span buffer, size_t max_line_length) - : start(buffer.begin()), end(buffer.end()), max_line_length(max_line_length), it(buffer.begin()) {} +LineReader::LineReader(std::string_view str, size_t max_line_length) + : m_str{str}, m_max_line_length{max_line_length}, m_it{str.begin()} {} std::optional LineReader::ReadLine() { - if (it == end) { + if (m_it == m_str.end()) { return std::nullopt; } - auto line_start = it; - size_t count = 0; - while (it != end) { + const auto line_start = m_it; + while (m_it != m_str.end()) { // Read a character from the incoming buffer and increment the iterator - auto c = static_cast(*it); - ++it; - ++count; + const bool new_line{*m_it == '\n'}; + ++m_it; // If the character we just consumed was \n, the line is terminated. // The \n itself does not count against max_line_length. - if (c == '\n') { - std::string_view line{reinterpret_cast(std::to_address(line_start)), count - 1}; + if (new_line) { + std::string_view line{line_start, m_it - 1}; if (!line.empty() && line.back() == '\r') line.remove_suffix(1); return line; @@ -44,16 +42,16 @@ std::optional LineReader::ReadLine() // If the character we just consumed gives us a line length greater // than max_line_length, and we are not at the end of the line (or buffer) yet, // that means the line we are currently reading is too long, and we throw. - if (count > max_line_length) { + if (static_cast(std::distance(line_start, m_it)) > m_max_line_length) { // Reset iterator - it = line_start; + m_it = line_start; throw std::runtime_error("max_line_length exceeded by LineReader"); } } // End of buffer reached without finding a \n or exceeding max_line_length. // Reset the iterator so the rest of the buffer can be read granularly // with ReadLength() and return null to indicate a line was not found. - it = line_start; + m_it = line_start; return std::nullopt; } @@ -62,18 +60,18 @@ std::string_view LineReader::ReadLength(size_t len) { if (len == 0) return {}; if (Remaining() < len) throw std::runtime_error("Not enough data in buffer"); - std::string_view out(reinterpret_cast(std::to_address(it)), len); - it += len; + std::string_view out(std::to_address(m_it), len); + m_it += len; return out; } size_t LineReader::Remaining() const { - return std::distance(it, end); + return std::distance(m_it, m_str.end()); } size_t LineReader::Consumed() const { - return std::distance(start, it); + return std::distance(m_str.begin(), m_it); } } // namespace util diff --git a/src/util/string.h b/src/util/string.h index 797d53447a1..e8f6ddf886a 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -265,14 +265,16 @@ template std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); } -struct LineReader { - const std::span::iterator start; - const std::span::iterator end; - const size_t max_line_length; - std::span::iterator it; +class LineReader +{ + const std::string_view m_str; + const size_t m_max_line_length; + std::string_view::iterator m_it; - explicit LineReader(std::span buffer, size_t max_line_length); - explicit LineReader(std::string_view str, size_t max_line_length) : LineReader{std::as_bytes(std::span{str}), max_line_length} {} +public: + explicit LineReader(std::span buffer, size_t max_line_length) + : LineReader{std::string_view{reinterpret_cast(buffer.data()), buffer.size()}, max_line_length} {} + explicit LineReader(std::string_view str, size_t max_line_length); /** * Returns a string from current iterator position up to (but not including) next \n From dff44e4c8f3070abe108965f216ec6b364ca882d Mon Sep 17 00:00:00 2001 From: Hodlinator <172445034+hodlinator@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:12:08 +0200 Subject: [PATCH 3/3] util: LineReader - Drop support for raw std::byte spans TorControlConnection and HTTPRemoteClient have been updated to use std::string receive buffers which mirrors approach in HTTPClient::ReadResponse(). --- src/httpserver.cpp | 2 +- src/httpserver.h | 2 +- src/test/fuzz/http_request.cpp | 2 +- src/torcontrol.cpp | 2 +- src/torcontrol.h | 2 +- src/util/string.h | 2 -- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/httpserver.cpp b/src/httpserver.cpp index a05d19db1f2..d7193f06206 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -861,7 +861,7 @@ void HTTPServer::SocketHandlerConnected(const IOReadiness& io_readiness) const } if (recv_ready || err_ready) { - std::byte buf[0x10000]; // typical socket buffer is 8K-64K + char buf[0x10000]; // typical socket buffer is 8K-64K const ssize_t nrecv{WITH_LOCK( client->m_sock_mutex, diff --git a/src/httpserver.h b/src/httpserver.h index 9031eb61e0a..91142aa2681 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -461,7 +461,7 @@ public: * we copy data from the socket buffer to the client object * and attempt to read HTTP requests from here. */ - std::vector m_recv_buffer{}; + std::string m_recv_buffer{}; //! Requests from a client must be processed in the order in which //! they were received, blocking on a per-client basis. We won't diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp index c992edcd178..82b270bdce4 100644 --- a/src/test/fuzz/http_request.cpp +++ b/src/test/fuzz/http_request.cpp @@ -25,7 +25,7 @@ FUZZ_TARGET(http_request) using util::LineReader; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - const std::vector http_buffer{ConsumeRandomLengthByteVector(fuzzed_data_provider, 4096)}; + const std::string http_buffer{fuzzed_data_provider.ConsumeRandomLengthString(4096)}; HTTPRequest http_request; LineReader reader(http_buffer, MAX_HEADERS_SIZE); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 9e00c7151ba..6f05ea4b091 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -149,7 +149,7 @@ bool TorControlConnection::ReceiveAndProcess() { if (!m_sock) return false; - std::byte buf[4096]; + char buf[4096]; ssize_t nread = m_sock->Recv(buf, sizeof(buf), MSG_DONTWAIT); if (nread < 0) { diff --git a/src/torcontrol.h b/src/torcontrol.h index 410cb0b368c..06a9d3e8bd3 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -109,7 +109,7 @@ private: /** Response handlers */ std::deque m_reply_handlers; /** Buffer for incoming data */ - std::vector m_recv_buffer; + std::string m_recv_buffer; /** Process complete lines from the receive buffer */ bool ProcessBuffer(); }; diff --git a/src/util/string.h b/src/util/string.h index e8f6ddf886a..d60fff0432c 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -272,8 +272,6 @@ class LineReader std::string_view::iterator m_it; public: - explicit LineReader(std::span buffer, size_t max_line_length) - : LineReader{std::string_view{reinterpret_cast(buffer.data()), buffer.size()}, max_line_length} {} explicit LineReader(std::string_view str, size_t max_line_length); /**