refactor: Replace Boost trim and to_lower with libxrpl helpers (#7995)

This commit is contained in:
Mayukha Vadari
2026-08-11 18:15:35 +00:00
committed by GitHub
parent a3147740f2
commit 6ca2fb84d4
13 changed files with 118 additions and 28 deletions

View File

@@ -125,9 +125,31 @@ struct ParsedUrl
bool
parseUrl(ParsedUrl& pUrl, std::string const& strUrl);
/**
* Remove leading and trailing ASCII whitespace.
*
* Whitespace is the fixed set " \t\n\v\f\r"; the current locale is not
* consulted, so the result depends only on the input.
*
* @param str The string to trim.
* @return @p str without leading or trailing whitespace.
*/
std::string
trimWhitespace(std::string str);
/**
* Fold ASCII upper case letters to lower case.
*
* Only 'A' through 'Z' are remapped; every other byte is left alone and the
* current locale is not consulted, so the result depends only on the input.
*
* @param str The string to fold.
* @return @p str with each ASCII upper case letter replaced by its lower case
* equivalent.
*/
std::string
toLower(std::string str);
std::optional<std::uint64_t>
toUInt64(std::string const& s);

View File

@@ -5,15 +5,15 @@
#include <xrpl/beast/net/IPEndpoint.h>
#include <boost/algorithm/hex.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/regex/v5/regbase.hpp>
#include <boost/regex/v5/regex.hpp>
#include <boost/regex/v5/regex_match.hpp>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
@@ -67,7 +67,7 @@ parseUrl(ParsedUrl& pUrl, std::string const& strUrl)
}
pUrl.scheme = smMatch[1];
boost::algorithm::to_lower(pUrl.scheme);
pUrl.scheme = toLower(pUrl.scheme);
pUrl.username = smMatch[2];
pUrl.password = smMatch[3];
std::string const domain = smMatch[4];
@@ -93,10 +93,42 @@ parseUrl(ParsedUrl& pUrl, std::string const& strUrl)
return true;
}
namespace {
// Deliberately not std::isspace / std::tolower: those consult the current C
// locale, so the same input could trim or fold differently depending on
// process-wide state set by something else entirely. Everything these helpers
// are used on (config keys and values, URL schemes, hex digests) is ASCII, and
// the callers want a fixed answer, so spell the ASCII rules out.
constexpr bool
isAsciiSpace(char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
}
constexpr char
toAsciiLower(char c)
{
return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
}
} // namespace
std::string
trimWhitespace(std::string str)
{
boost::trim(str);
auto const end = std::ranges::find_if_not(str | std::views::reverse, isAsciiSpace).base();
str.erase(end, str.end());
str.erase(str.begin(), std::ranges::find_if_not(str, isAsciiSpace));
return str;
}
std::string
toLower(std::string str)
{
std::ranges::transform(str, str.begin(), toAsciiLower);
return str;
}

View File

@@ -1,11 +1,11 @@
#include <xrpl/crypto/RFC1751.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/range/adaptor/copied.hpp>
#include <cctype>
@@ -397,7 +397,7 @@ RFC1751::getKeyFromEnglish(std::string& strKey, std::string const& strHuman)
std::string strTrimmed(strHuman);
boost::algorithm::trim(strTrimmed);
strTrimmed = trimWhitespace(strTrimmed);
boost::algorithm::split(
vWords, strTrimmed, boost::algorithm::is_space(), boost::algorithm::token_compress_on);

View File

@@ -23,8 +23,6 @@
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/server/Wallet.h>
#include <boost/algorithm/string/trim.hpp>
#include <cstddef>
#include <cstdint>
#include <exception>
@@ -277,7 +275,7 @@ loadValidatorToken(std::vector<std::string> const& blob, beast::Journal journal)
[](std::size_t init, std::string const& s) { return init + s.size(); }));
for (auto const& line : blob)
tokenStr += boost::algorithm::trim_copy(line);
tokenStr += trimWhitespace(line);
tokenStr = base64Decode(tokenStr);
@@ -653,7 +651,7 @@ ManifestCache::load(
[](std::size_t init, std::string const& s) { return init + s.size(); }));
for (auto const& line : configRevocation)
revocationStr += boost::algorithm::trim_copy(line);
revocationStr += trimWhitespace(line);
auto mo = deserializeManifest(base64Decode(revocationStr));

View File

@@ -1,5 +1,6 @@
#include <xrpl/server/Port.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/core/LexicalCast.h>
@@ -9,7 +10,6 @@
#include <xrpl/config/Constants.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/impl/network_v4.ipp>
#include <boost/asio/ip/impl/network_v6.ipp>
@@ -98,7 +98,7 @@ populate(
while (std::getline(ss, ip, ','))
{
boost::algorithm::trim(ip);
ip = trimWhitespace(ip);
bool v4 = false;
boost::asio::ip::network_v4 v4Net;
boost::asio::ip::network_v6 v6Net;

View File

@@ -290,4 +290,44 @@ TEST_F(StringUtilitiesTest, to_string)
EXPECT_EQ(result, "hello");
}
TEST_F(StringUtilitiesTest, trimWhitespace)
{
EXPECT_EQ(trimWhitespace(""), "");
EXPECT_EQ(trimWhitespace(" "), "");
EXPECT_EQ(trimWhitespace("abc"), "abc");
EXPECT_EQ(trimWhitespace(" abc"), "abc");
EXPECT_EQ(trimWhitespace("abc "), "abc");
EXPECT_EQ(trimWhitespace(" \t\n\v\f\r abc \t\n\v\f\r "), "abc");
// Interior whitespace is preserved.
EXPECT_EQ(trimWhitespace(" a b\tc "), "a b\tc");
}
TEST_F(StringUtilitiesTest, toLower)
{
EXPECT_EQ(toLower(""), "");
EXPECT_EQ(toLower("ABC"), "abc");
EXPECT_EQ(toLower("AbC123"), "abc123");
EXPECT_EQ(toLower("already lower"), "already lower");
// Only 'A'-'Z' are remapped. Neighbouring punctuation and digits, which a
// buggy range check could catch, must survive untouched.
EXPECT_EQ(toLower("@[`{_^"), "@[`{_^");
}
// Both helpers are documented as depending only on their input. Guard that by
// checking the bytes just outside ASCII, which a locale-aware isspace/tolower
// could classify differently.
TEST_F(StringUtilitiesTest, trimAndLowerIgnoreLocale)
{
// 0xA0 is NO-BREAK SPACE in Latin-1 and is whitespace to some locales.
std::string const nbsp("\xA0", 1);
EXPECT_EQ(trimWhitespace(nbsp), nbsp);
EXPECT_EQ(trimWhitespace(" " + nbsp + " "), nbsp);
// 0xC0 is LATIN CAPITAL LETTER A WITH GRAVE in Latin-1.
std::string const agrave("\xC0", 1);
EXPECT_EQ(toLower(agrave), agrave);
}
} // namespace xrpl

View File

@@ -9,6 +9,7 @@
#include <xrpl/basics/FileUtilities.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/net/IPAddressConversion.h>
@@ -24,7 +25,6 @@
#include <xrpl/resource/Fees.h>
#include <xrpl/server/InfoSub.h>
#include <boost/algorithm/string/trim.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/icl/interval_set.hpp>
@@ -371,7 +371,7 @@ GRPCServerImpl::GRPCServerImpl(Application& app)
std::string ip;
while (std::getline(ss, ip, ','))
{
boost::algorithm::trim(ip);
ip = trimWhitespace(ip);
auto const addr = boost::asio::ip::make_address(ip);
if (addr.is_unspecified())

View File

@@ -6,6 +6,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/SlabAllocator.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/net/IPEndpoint.h>
@@ -21,7 +22,6 @@
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/process/v1/args.hpp>
#include <boost/process/v1/child.hpp> // IWYU pragma: keep
#include <boost/process/v1/exe.hpp>
@@ -211,7 +211,7 @@ public:
boost::split(v, patterns, boost::algorithm::is_any_of(","));
selectors_.reserve(v.size());
std::ranges::for_each(v, [this](std::string s) {
boost::trim(s);
s = trimWhitespace(s);
if (selectors_.empty() || !s.empty())
selectors_.emplace_back(beast::unit_test::Selector::ModeT::Automatch, s);
});
@@ -614,7 +614,7 @@ run(int argc, char** argv)
std::vector<std::uint32_t> result;
for (auto& s : strVec)
{
boost::trim(s);
s = trimWhitespace(s);
if (!s.empty())
result.push_back(std::stoi(s));
}

View File

@@ -20,7 +20,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format/free_funcs.hpp>
@@ -185,7 +184,7 @@ parseIniFile(std::string const& strInput, bool const bTrim)
for (auto& strValue : vLines)
{
if (bTrim)
boost::algorithm::trim(strValue);
strValue = trimWhitespace(strValue);
if (strValue.empty() || strValue[0] == '#')
{

View File

@@ -8,6 +8,7 @@
#include <xrpld/rpc/detail/WSInfoSub.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base64.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/make_SSLContext.h>
@@ -44,7 +45,6 @@
#include <xrpl/server/WSSession.h>
#include <xrpl/server/detail/JSONRPCUtil.h>
#include <boost/algorithm/string/trim.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
@@ -113,7 +113,7 @@ authorized(Port const& port, std::map<std::string, std::string> const& h)
if ((it == h.end()) || (!it->second.starts_with("Basic ")))
return false;
std::string strUserPass64 = it->second.substr(6);
boost::trim(strUserPass64);
strUserPass64 = trimWhitespace(strUserPass64);
std::string const strUserPass = base64Decode(strUserPass64);
std::string::size_type const nColon = strUserPass.find(':');
if (nColon == std::string::npos)

View File

@@ -5,6 +5,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_forwards.h>
@@ -22,7 +23,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/format/free_funcs.hpp>
#include <array>
@@ -57,7 +57,7 @@ injectSLE(json::Value& jv, SLE const& sle)
auto const& hash = sle.getFieldH128(sfEmailHash);
Blob const b(hash.begin(), hash.end());
std::string md5 = strHex(makeSlice(b));
boost::to_lower(md5);
md5 = toLower(md5);
// VFALCO TODO Give a name to this constant and move it
// to a more visible location.
jv[jss::urlgravatar] = str(boost::format("https://www.gravatar.com/avatar/%s") % md5);

View File

@@ -3,14 +3,13 @@
#include <xrpld/app/misc/SHAMapStore.h>
#include <xrpld/rpc/Context.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <cstdint>
#include <limits>
#include <string>
@@ -38,7 +37,7 @@ doCanDelete(rpc::JsonContext& context)
else
{
std::string canDeleteStr = canDelete.asString();
boost::to_lower(canDeleteStr);
canDeleteStr = toLower(canDeleteStr);
if (canDeleteStr.find_first_not_of("0123456789") == std::string::npos)
{

View File

@@ -2,6 +2,7 @@
#include <xrpld/rpc/Context.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>
@@ -14,7 +15,6 @@
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <cstddef>
@@ -106,7 +106,7 @@ ServerDefinitions::translate(std::string const& inp)
std::string token = inpToProcess.substr(0, pos);
if (token.size() > 1)
{
boost::algorithm::to_lower(token);
token = toLower(token);
token[0] -= ('a' - 'A');
out += token;
}