// // impl/read_until.hpp // ~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #ifndef ASIO_IMPL_READ_UNTIL_HPP #define ASIO_IMPL_READ_UNTIL_HPP #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include #include #include #include #include "asio/associator.hpp" #include "asio/buffer.hpp" #include "asio/buffers_iterator.hpp" #include "asio/detail/base_from_cancellation_state.hpp" #include "asio/detail/bind_handler.hpp" #include "asio/detail/handler_cont_helpers.hpp" #include "asio/detail/handler_tracking.hpp" #include "asio/detail/handler_type_requirements.hpp" #include "asio/detail/limits.hpp" #include "asio/detail/non_const_lvalue.hpp" #include "asio/detail/throw_error.hpp" #include "asio/detail/push_options.hpp" namespace asio { namespace detail { // Algorithm that finds a subsequence of equal values in a sequence. Returns // (iterator,true) if a full match was found, in which case the iterator // points to the beginning of the match. Returns (iterator,false) if a // partial match was found at the end of the first sequence, in which case // the iterator points to the beginning of the partial match. Returns // (last1,false) if no full or partial match was found. template std::pair partial_search( Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) { for (Iterator1 iter1 = first1; iter1 != last1; ++iter1) { Iterator1 test_iter1 = iter1; Iterator2 test_iter2 = first2; for (;; ++test_iter1, ++test_iter2) { if (test_iter2 == last2) return std::make_pair(iter1, true); if (test_iter1 == last1) { if (test_iter2 != first2) return std::make_pair(iter1, false); else break; } if (*test_iter1 != *test_iter2) break; } } return std::make_pair(last1, false); } } // namespace detail #if !defined(ASIO_NO_DYNAMIC_BUFFER_V1) template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, char delim, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), delim, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, char delim, asio::error_code& ec, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { decay_t b( static_cast(buffers)); std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v1::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = b.data(); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. iterator iter = std::find(start_pos, end, delim); if (iter != end) { // Found a match. We're done. ec = asio::error_code(); return iter - begin + 1; } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); b.commit(s.read_some(b.prepare(bytes_to_read), ec)); if (ec) return 0; } } template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, ASIO_STRING_VIEW_PARAM delim, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), delim, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, ASIO_STRING_VIEW_PARAM delim, asio::error_code& ec, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { decay_t b( static_cast(buffers)); std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v1::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = b.data(); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. std::pair result = detail::partial_search( start_pos, end, delim.begin(), delim.end()); if (result.first != end) { if (result.second) { // Full match. We're done. ec = asio::error_code(); return result.first - begin + delim.length(); } else { // Partial match. Next search needs to start from beginning of match. search_position = result.first - begin; } } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); b.commit(s.read_some(b.prepare(bytes_to_read), ec)); if (ec) return 0; } } #if !defined(ASIO_NO_EXTENSIONS) #if defined(ASIO_HAS_BOOST_REGEX) namespace detail { struct regex_match_flags { template operator T() const { return T::match_default | T::match_partial; } }; } // namespace detail template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, const boost::basic_regex& expr, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), expr, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, const boost::basic_regex& expr, asio::error_code& ec, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { decay_t b( static_cast(buffers)); std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v1::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = b.data(); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. boost::match_results>::allocator_type> match_results; if (regex_search(start_pos, end, match_results, expr, detail::regex_match_flags())) { if (match_results[0].matched) { // Full match. We're done. ec = asio::error_code(); return match_results[0].second - begin; } else { // Partial match. Next search needs to start from beginning of match. search_position = match_results[0].first - begin; } } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); b.commit(s.read_some(b.prepare(bytes_to_read), ec)); if (ec) return 0; } } #endif // defined(ASIO_HAS_BOOST_REGEX) template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, MatchCondition match_condition, constraint_t< is_match_condition::value >, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), match_condition, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v1&& buffers, MatchCondition match_condition, asio::error_code& ec, constraint_t< is_match_condition::value >, constraint_t< is_dynamic_buffer_v1>::value >, constraint_t< !is_dynamic_buffer_v2>::value >) { decay_t b( static_cast(buffers)); std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v1::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = b.data(); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. std::pair result = match_condition(start_pos, end); if (result.second) { // Full match. We're done. ec = asio::error_code(); return result.first - begin; } else if (result.first != end) { // Partial match. Next search needs to start from beginning of match. search_position = result.first - begin; } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); b.commit(s.read_some(b.prepare(bytes_to_read), ec)); if (ec) return 0; } } #if !defined(ASIO_NO_IOSTREAM) template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, char delim) { return read_until(s, basic_streambuf_ref(b), delim); } template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, char delim, asio::error_code& ec) { return read_until(s, basic_streambuf_ref(b), delim, ec); } template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, ASIO_STRING_VIEW_PARAM delim) { return read_until(s, basic_streambuf_ref(b), delim); } template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, ASIO_STRING_VIEW_PARAM delim, asio::error_code& ec) { return read_until(s, basic_streambuf_ref(b), delim, ec); } #if defined(ASIO_HAS_BOOST_REGEX) template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, const boost::basic_regex& expr) { return read_until(s, basic_streambuf_ref(b), expr); } template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, const boost::basic_regex& expr, asio::error_code& ec) { return read_until(s, basic_streambuf_ref(b), expr, ec); } #endif // defined(ASIO_HAS_BOOST_REGEX) template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, MatchCondition match_condition, constraint_t::value>) { return read_until(s, basic_streambuf_ref(b), match_condition); } template inline std::size_t read_until(SyncReadStream& s, asio::basic_streambuf& b, MatchCondition match_condition, asio::error_code& ec, constraint_t::value>) { return read_until(s, basic_streambuf_ref(b), match_condition, ec); } #endif // !defined(ASIO_NO_IOSTREAM) #endif // !defined(ASIO_NO_EXTENSIONS) #endif // !defined(ASIO_NO_DYNAMIC_BUFFER_V1) template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, char delim, constraint_t< is_dynamic_buffer_v2::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), delim, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, char delim, asio::error_code& ec, constraint_t< is_dynamic_buffer_v2::value >) { DynamicBuffer_v2& b = buffers; std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v2::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = const_cast(b).data(0, b.size()); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. iterator iter = std::find(start_pos, end, delim); if (iter != end) { // Found a match. We're done. ec = asio::error_code(); return iter - begin + 1; } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); std::size_t pos = b.size(); b.grow(bytes_to_read); std::size_t bytes_transferred = s.read_some(b.data(pos, bytes_to_read), ec); b.shrink(bytes_to_read - bytes_transferred); if (ec) return 0; } } template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, ASIO_STRING_VIEW_PARAM delim, constraint_t< is_dynamic_buffer_v2::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), delim, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, ASIO_STRING_VIEW_PARAM delim, asio::error_code& ec, constraint_t< is_dynamic_buffer_v2::value >) { DynamicBuffer_v2& b = buffers; std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v2::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = const_cast(b).data(0, b.size()); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. std::pair result = detail::partial_search( start_pos, end, delim.begin(), delim.end()); if (result.first != end) { if (result.second) { // Full match. We're done. ec = asio::error_code(); return result.first - begin + delim.length(); } else { // Partial match. Next search needs to start from beginning of match. search_position = result.first - begin; } } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); std::size_t pos = b.size(); b.grow(bytes_to_read); std::size_t bytes_transferred = s.read_some(b.data(pos, bytes_to_read), ec); b.shrink(bytes_to_read - bytes_transferred); if (ec) return 0; } } #if !defined(ASIO_NO_EXTENSIONS) #if defined(ASIO_HAS_BOOST_REGEX) template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, const boost::basic_regex& expr, constraint_t< is_dynamic_buffer_v2::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), expr, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, const boost::basic_regex& expr, asio::error_code& ec, constraint_t< is_dynamic_buffer_v2::value >) { DynamicBuffer_v2& b = buffers; std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v2::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = const_cast(b).data(0, b.size()); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. boost::match_results>::allocator_type> match_results; if (regex_search(start_pos, end, match_results, expr, detail::regex_match_flags())) { if (match_results[0].matched) { // Full match. We're done. ec = asio::error_code(); return match_results[0].second - begin; } else { // Partial match. Next search needs to start from beginning of match. search_position = match_results[0].first - begin; } } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); std::size_t pos = b.size(); b.grow(bytes_to_read); std::size_t bytes_transferred = s.read_some(b.data(pos, bytes_to_read), ec); b.shrink(bytes_to_read - bytes_transferred); if (ec) return 0; } } #endif // defined(ASIO_HAS_BOOST_REGEX) template inline std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, MatchCondition match_condition, constraint_t< is_match_condition::value >, constraint_t< is_dynamic_buffer_v2::value >) { asio::error_code ec; std::size_t bytes_transferred = read_until(s, static_cast(buffers), match_condition, ec); asio::detail::throw_error(ec, "read_until"); return bytes_transferred; } template std::size_t read_until(SyncReadStream& s, DynamicBuffer_v2 buffers, MatchCondition match_condition, asio::error_code& ec, constraint_t< is_match_condition::value >, constraint_t< is_dynamic_buffer_v2::value >) { DynamicBuffer_v2& b = buffers; std::size_t search_position = 0; for (;;) { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v2::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = const_cast(b).data(0, b.size()); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position; iterator end = iterator::end(data_buffers); // Look for a match. std::pair result = match_condition(start_pos, end); if (result.second) { // Full match. We're done. ec = asio::error_code(); return result.first - begin; } else if (result.first != end) { // Partial match. Next search needs to start from beginning of match. search_position = result.first - begin; } else { // No match. Next search can start with the new data. search_position = end - begin; } // Check if buffer is full. if (b.size() == b.max_size()) { ec = error::not_found; return 0; } // Need more data. std::size_t bytes_to_read = std::min( std::max(512, b.capacity() - b.size()), std::min(65536, b.max_size() - b.size())); std::size_t pos = b.size(); b.grow(bytes_to_read); std::size_t bytes_transferred = s.read_some(b.data(pos, bytes_to_read), ec); b.shrink(bytes_to_read - bytes_transferred); if (ec) return 0; } } #endif // !defined(ASIO_NO_EXTENSIONS) #if !defined(ASIO_NO_DYNAMIC_BUFFER_V1) namespace detail { template class read_until_delim_op_v1 : public base_from_cancellation_state { public: template read_until_delim_op_v1(AsyncReadStream& stream, BufferSequence&& buffers, char delim, ReadHandler& handler) : base_from_cancellation_state( handler, enable_partial_cancellation()), stream_(stream), buffers_(static_cast(buffers)), delim_(delim), start_(0), search_position_(0), handler_(static_cast(handler)) { } read_until_delim_op_v1(const read_until_delim_op_v1& other) : base_from_cancellation_state(other), stream_(other.stream_), buffers_(other.buffers_), delim_(other.delim_), start_(other.start_), search_position_(other.search_position_), handler_(other.handler_) { } read_until_delim_op_v1(read_until_delim_op_v1&& other) : base_from_cancellation_state( static_cast&&>(other)), stream_(other.stream_), buffers_(static_cast(other.buffers_)), delim_(other.delim_), start_(other.start_), search_position_(other.search_position_), handler_(static_cast(other.handler_)) { } void operator()(asio::error_code ec, std::size_t bytes_transferred, int start = 0) { const std::size_t not_found = (std::numeric_limits::max)(); std::size_t bytes_to_read; switch (start_ = start) { case 1: for (;;) { { // Determine the range of the data to be searched. typedef typename DynamicBuffer_v1::const_buffers_type buffers_type; typedef buffers_iterator iterator; buffers_type data_buffers = buffers_.data(); iterator begin = iterator::begin(data_buffers); iterator start_pos = begin + search_position_; iterator end = iterator::end(data_buffers); // Look for a match. iterator iter = std::find(start_pos, end, delim_); if (iter != end) { // Found a match. We're done. search_position_ = iter - begin + 1; bytes_to_read = 0; } // No match yet. Check if buffer is full. else if (buffers_.size() == buffers_.max_size()) { search_position_ = not_found; bytes_to_read = 0; } // Need to read some more data. else { // Next search can start with the new data. search_position_ = end - begin; bytes_to_read = std::min( std::max(512, buffers_.capacity() - buffers_.size()), std::min(65536, buffers_.max_size() - buffers_.size())); } } // Check if we're done. if (!start && bytes_to_read == 0) break; // Start a new asynchronous read operation to obtain more data. { ASIO_HANDLER_LOCATION(( __FILE__, __LINE__, "async_read_until")); stream_.async_read_some(buffers_.prepare(bytes_to_read), static_cast(*this)); } return; default: buffers_.commit(bytes_transferred); if (ec || bytes_transferred == 0) break; if (this->cancelled() != cancellation_type::none) { ec = error::operation_aborted; break; } } const asio::error_code result_ec = (search_position_ == not_found) ? error::not_found : ec; const std::size_t result_n = (ec || search_position_ == not_found) ? 0 : search_position_; static_cast(handler_)(result_ec, result_n); } } //private: AsyncReadStream& stream_; DynamicBuffer_v1 buffers_; char delim_; int start_; std::size_t search_position_; ReadHandler handler_; }; template inline bool asio_handler_is_continuation( read_until_delim_op_v1* this_handler) { return this_handler->start_ == 0 ? true : asio_handler_cont_helpers::is_continuation( this_handler->handler_); } template class initiate_async_read_until_delim_v1 { public: typedef typename AsyncReadStream::executor_type executor_type; explicit initiate_async_read_until_delim_v1(AsyncReadStream& stream) : stream_(stream) { } executor_type get_executor() const noexcept { return stream_.get_executor(); } template void operator()(ReadHandler&& handler, DynamicBuffer_v1&& buffers, char delim) const { // If you get an error on the following line it means that your handler // does not meet the documented type requirements for a ReadHandler. ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; non_const_lvalue handler2(handler); read_until_delim_op_v1, decay_t>( stream_, static_cast(buffers), delim, handler2.value)(asio::error_code(), 0, 1); } private: AsyncReadStream& stream_; }; } // namespace detail #if !defined(GENERATING_DOCUMENTATION) template