From 218179642881c41f482f8bd407042b08fa4794dd Mon Sep 17 00:00:00 2001 From: Alan de Freitas Date: Thu, 23 Apr 2026 22:50:15 -0500 Subject: [PATCH] fix: decoded query-size tracking in url_base::edit_params Latent issue exposed by b54e9e8, not introduced by it. fix #989 --- include/boost/url/impl/url_base.hpp | 14 +++---- test/unit/params_encoded_ref.cpp | 5 +++ test/unit/params_ref.cpp | 61 +++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/include/boost/url/impl/url_base.hpp b/include/boost/url/impl/url_base.hpp index be3c35bef..476127892 100644 --- a/include/boost/url/impl/url_base.hpp +++ b/include/boost/url/impl/url_base.hpp @@ -3159,7 +3159,12 @@ edit_params( BOOST_ASSERT(pos1 <= impl_.offset(id_frag)); // calc decoded size of old range, - // minus one if '?' or '&' prefixed + // minus one for the leading '?' which is + // not counted in decoded_[id_query]. + // dn0 may be -1 here when the old range is + // empty and the query was non-empty; the + // matching subtraction on dn below cancels + // that out when the delta is taken. auto dn0 = static_cast( detail::decode_bytes_unsafe( @@ -3168,8 +3173,6 @@ edit_params( pos1 - pos0))); if(impl_.len(id_query) > 0) dn0 -= 1; - if(dn0 < 0) - dn0 = 0; //------------------------------------------------ // @@ -3267,16 +3270,13 @@ edit_params( } } - // calc decoded size of new range, - // minus one if '?' or '&' prefixed + // calc decoded size of new range; see dn0. auto dn = static_cast( detail::decode_bytes_unsafe( core::string_view(dest0, dest - dest0))); if(impl_.len(id_query) > 0) dn -= 1; - if(dn < 0) - dn = 0; if(dn >= dn0) impl_.decoded_[id_query] += diff --git a/test/unit/params_encoded_ref.cpp b/test/unit/params_encoded_ref.cpp index 10811b234..8004958cc 100644 --- a/test/unit/params_encoded_ref.cpp +++ b/test/unit/params_encoded_ref.cpp @@ -121,6 +121,11 @@ struct params_encoded_ref_test params_encoded_ref ps(u.encoded_params()); f(ps); BOOST_TEST_EQ(u.encoded_query(), s1); + BOOST_TEST_EQ( + u.encoded_query().decoded_size(), + pct_string_view(s1).decoded_size()); + BOOST_TEST_NO_THROW(u.encoded_target()); + BOOST_TEST_NO_THROW(u.encoded_resource()); if(! BOOST_TEST_EQ( ps.size(), init.size())) return; diff --git a/test/unit/params_ref.cpp b/test/unit/params_ref.cpp index cf68456ba..9c08bfc12 100644 --- a/test/unit/params_ref.cpp +++ b/test/unit/params_ref.cpp @@ -142,6 +142,11 @@ struct params_ref_test params_ref ps(u.params()); f(ps); BOOST_TEST_EQ(u.encoded_query(), s1); + BOOST_TEST_EQ( + u.encoded_query().decoded_size(), + pct_string_view(s1).decoded_size()); + BOOST_TEST_NO_THROW(u.encoded_target()); + BOOST_TEST_NO_THROW(u.encoded_resource()); if(! BOOST_TEST_EQ( ps.size(), init.size())) return; @@ -814,6 +819,62 @@ struct params_ref_test check(f, "?k0&k1=&k2=key", "k0&k1=" BIGSTR "&k2=key", { {"k0",no_value}, {"k1",BIGSTR}, {"k2","key"} }); } + + // issue #989: encoded_target() asserts after + // successive params().set() on a fresh url. + // Regression for broken decoded_[id_query] + // bookkeeping in url_base::edit_params. + { + url u; + u.params().set("a", "b"); + BOOST_TEST_EQ(u.encoded_query(), "a=b"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 3u); + BOOST_TEST_EQ(u.encoded_target(), "?a=b"); + + u.params().set("c", "d"); + BOOST_TEST_EQ(u.encoded_query(), "a=b&c=d"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 7u); + BOOST_TEST_EQ(u.encoded_target(), "?a=b&c=d"); + } + + // Related: erase first param must leave a + // consistent decoded_[id_query]. + { + url u("?a=b&c=d"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 7u); + u.params().erase(u.params().begin()); + BOOST_TEST_EQ(u.encoded_query(), "c=d"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 3u); + BOOST_TEST_EQ(u.encoded_target(), "?c=d"); + } + + // Related: erase last param must leave a + // consistent decoded_[id_query]. + { + url u("?a=b&c=d"); + u.params().erase(std::next(u.params().begin())); + BOOST_TEST_EQ(u.encoded_query(), "a=b"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 3u); + BOOST_TEST_EQ(u.encoded_target(), "?a=b"); + } + + // Related: insert at front of non-empty query. + { + url u("?x=y"); + u.params().insert(u.params().begin(), {"a", "b"}); + BOOST_TEST_EQ(u.encoded_query(), "a=b&x=y"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 7u); + BOOST_TEST_EQ(u.encoded_target(), "?a=b&x=y"); + } + + // Related: append to non-empty query. + { + url u("?x=y"); + u.params().append({"a", "b"}); + BOOST_TEST_EQ(u.encoded_query(), "x=y&a=b"); + BOOST_TEST_EQ(u.encoded_query().decoded_size(), 7u); + BOOST_TEST_EQ(u.encoded_target(), "?x=y&a=b"); + } } static