xrpld
Loading...
Searching...
No Matches
libxrpl/basics/StringUtilities.cpp
1#include <xrpl/basics/StringUtilities.h>
2
3#include <xrpl/basics/Blob.h>
4#include <xrpl/beast/core/LexicalCast.h>
5#include <xrpl/beast/net/IPEndpoint.h>
6
7#include <boost/algorithm/hex.hpp>
8#include <boost/regex/v5/regbase.hpp>
9#include <boost/regex/v5/regex.hpp>
10#include <boost/regex/v5/regex_match.hpp>
11
12#include <algorithm>
13#include <cstdint>
14#include <iterator>
15#include <optional>
16#include <ranges>
17#include <string>
18#include <string_view>
19
20namespace xrpl {
21
22std::string
23sqlBlobLiteral(Blob const& blob)
24{
26
27 j.reserve((blob.size() * 2) + 3);
28 j.push_back('X');
29 j.push_back('\'');
30 boost::algorithm::hex(blob.begin(), blob.end(), std::back_inserter(j));
31 j.push_back('\'');
32
33 return j;
34}
35
36bool
37parseUrl(ParsedUrl& pUrl, std::string const& strUrl)
38{
39 // scheme://username:password@hostname:port/rest
40 static boost::regex const kReUrl(
41 "(?i)\\`\\s*"
42 // required scheme
43 "([[:alpha:]][-+.[:alpha:][:digit:]]*?):"
44 // We choose to support only URIs whose `hier-part` has the form
45 // `"//" authority path-abempty`.
46 "//"
47 // optional userinfo
48 "(?:([^:@/]*?)(?::([^@/]*?))?@)?"
49 // optional host
50 "([[:digit:]:]*[[:digit:]]|\\[[^]]+\\]|[^:/?#]*?)"
51 // optional port
52 "(?::([[:digit:]]+))?"
53 // optional path
54 "(/.*)?"
55 "\\s*?\\'");
56 boost::smatch smMatch;
57
58 // Bail if there is no match.
59 try
60 {
61 if (!boost::regex_match(strUrl, smMatch, kReUrl))
62 return false;
63 }
64 catch (...)
65 {
66 return false;
67 }
68
69 pUrl.scheme = smMatch[1];
70 pUrl.scheme = toLower(pUrl.scheme);
71 pUrl.username = smMatch[2];
72 pUrl.password = smMatch[3];
73 std::string const domain = smMatch[4];
74 // We need to use Endpoint to parse the domain to
75 // strip surrounding brackets from IPv6 addresses,
76 // e.g. [::1] => ::1.
77 auto const result = beast::ip::Endpoint::fromStringChecked(domain);
78 pUrl.domain = result ? result->address().to_string() : domain;
79 std::string const port = smMatch[5];
80 if (!port.empty())
81 {
83
84 // For inputs larger than 2^32-1 (65535), lexicalCast returns 0.
85 // parseUrl returns false for such inputs.
86 if (pUrl.port == 0)
87 {
88 return false;
89 }
90 }
91 pUrl.path = smMatch[6];
92
93 return true;
94}
95
96namespace {
97
98// Deliberately not std::isspace / std::tolower: those consult the current C
99// locale, so the same input could trim or fold differently depending on
100// process-wide state set by something else entirely. Everything these helpers
101// are used on (config keys and values, URL schemes, hex digests) is ASCII, and
102// the callers want a fixed answer, so spell the ASCII rules out.
103
104constexpr bool
105isAsciiSpace(char c)
106{
107 return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
108}
109
110constexpr char
111toAsciiLower(char c)
112{
113 return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
114}
115
116} // namespace
117
118std::string
120{
121 auto const end = std::ranges::find_if_not(str | std::views::reverse, isAsciiSpace).base();
122 str.erase(end, str.end());
123 str.erase(str.begin(), std::ranges::find_if_not(str, isAsciiSpace));
124
125 return str;
126}
127
130{
131 std::ranges::transform(str, str.begin(), toAsciiLower);
132 return str;
133}
134
137{
138 std::uint64_t result = 0;
139 if (beast::lexicalCastChecked(result, s))
140 return result;
141 return std::nullopt;
142}
143
144bool
146{
147 // The domain must be between 4 and 128 characters long
148 if (domain.size() < 4 || domain.size() > 128)
149 return false;
150
151 // This regular expression should do a decent job of weeding out
152 // obviously wrong domain names but it isn't perfect. It does not
153 // really support IDNs. If this turns out to be an issue, a more
154 // thorough regex can be used or this check can just be removed.
155 static boost::regex const kRE(
156 "^" // Beginning of line
157 "(" // Beginning of a segment
158 "(?!-)" // - must not begin with '-'
159 "[a-zA-Z0-9-]{1,63}" // - only alphanumeric and '-'
160 "(?<!-)" // - must not end with '-'
161 "\\." // segment separator
162 ")+" // 1 or more segments
163 "[A-Za-z]{2,63}" // TLD
164 "$" // End of line
165 ,
166 boost::regex_constants::optimize);
167
168 return boost::regex_match(domain.begin(), domain.end(), kRE);
169}
170
171} // namespace xrpl
T back_inserter(T... args)
T begin(T... args)
static std::optional< Endpoint > fromStringChecked(std::string const &s)
Create an Endpoint from a string.
T empty(T... args)
T end(T... args)
T erase(T... args)
T find_if_not(T... args)
constexpr bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
constexpr Out lexicalCast(In in, Out defaultValue=Out())
Convert from one type to another.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string toLower(std::string str)
Fold ASCII upper case letters to lower case.
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
std::string trimWhitespace(std::string str)
Remove leading and trailing ASCII whitespace.
std::optional< std::uint64_t > toUInt64(std::string const &s)
std::string sqlBlobLiteral(Blob const &blob)
Format arbitrary binary data as an SQLite "blob literal".
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:11
bool parseUrl(ParsedUrl &pUrl, std::string const &strUrl)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
std::optional< std::uint16_t > port
std::string password
std::string username
T transform(T... args)