Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #ifdef HAVE_CONFIG_H
18 : #include "config.h"
19 : #endif
20 :
21 : #include "string_utils.h"
22 :
23 : #include <fmt/core.h>
24 : #include <fmt/ranges.h>
25 : #include <fmt/compile.h>
26 :
27 : #include <sstream>
28 : #include <cctype>
29 : #include <algorithm>
30 : #include <ostream>
31 : #include <iomanip>
32 : #include <stdexcept>
33 : #include <ios>
34 : #include <charconv>
35 : #include <string_view>
36 : #ifdef _WIN32
37 : #include <windows.h>
38 : #include <oleauto.h>
39 : #endif
40 :
41 : #include "json_utils.h"
42 :
43 : namespace jami {
44 :
45 : namespace json {
46 : static Json::CharReaderBuilder
47 41 : getJsonReaderBuilder()
48 : {
49 41 : Json::CharReaderBuilder builder;
50 41 : builder["collectComments"] = false;
51 41 : return builder;
52 0 : }
53 :
54 : static Json::StreamWriterBuilder
55 41 : getJsonWriterBuilder()
56 : {
57 41 : Json::StreamWriterBuilder builder;
58 41 : builder["commentStyle"] = "None";
59 41 : builder["indentation"] = "";
60 41 : return builder;
61 0 : }
62 :
63 : const Json::CharReaderBuilder rbuilder = getJsonReaderBuilder();
64 : const Json::StreamWriterBuilder wbuilder = getJsonWriterBuilder();
65 : } // namespace json
66 :
67 : const std::string&
68 692 : userAgent()
69 : {
70 715 : static const std::string USER_AGENT = fmt::format("{:s} ({:s}/{:s})", PACKAGE_NAME, platform(), arch());
71 692 : return USER_AGENT;
72 : }
73 :
74 : #ifdef _WIN32
75 : std::wstring
76 : to_wstring(const std::string& str, int codePage)
77 : {
78 : int srcLength = (int) str.length();
79 : int requiredSize = MultiByteToWideChar(codePage, 0, str.c_str(), srcLength, nullptr, 0);
80 : if (!requiredSize) {
81 : throw std::runtime_error("Unable to convert string to wstring");
82 : }
83 : std::wstring result((size_t) requiredSize, 0);
84 : if (!MultiByteToWideChar(codePage, 0, str.c_str(), srcLength, &(*result.begin()), requiredSize)) {
85 : throw std::runtime_error("Unable to convert string to wstring");
86 : }
87 : return result;
88 : }
89 :
90 : std::string
91 : to_string(const std::wstring& wstr, int codePage)
92 : {
93 : int srcLength = (int) wstr.length();
94 : int requiredSize = WideCharToMultiByte(codePage, 0, wstr.c_str(), srcLength, nullptr, 0, 0, 0);
95 : if (!requiredSize) {
96 : throw std::runtime_error("Unable to convert wstring to string");
97 : }
98 : std::string result((size_t) requiredSize, 0);
99 : if (!WideCharToMultiByte(codePage, 0, wstr.c_str(), srcLength, &(*result.begin()), requiredSize, 0, 0)) {
100 : throw std::runtime_error("Unable to convert wstring to string");
101 : }
102 : return result;
103 : }
104 : #endif
105 :
106 : std::string
107 67 : to_string(double value)
108 : {
109 268 : return fmt::format(FMT_COMPILE("{:.16G}"), value);
110 : }
111 :
112 : std::string
113 826 : to_hex_string(uint64_t id)
114 : {
115 3304 : return fmt::format(FMT_COMPILE("{:016x}"), id);
116 : }
117 :
118 : uint64_t
119 1 : from_hex_string(const std::string& str)
120 : {
121 : uint64_t id;
122 1 : if (auto [p, ec] = std::from_chars(str.data(), str.data() + str.size(), id, 16); ec != std::errc()) {
123 0 : throw std::invalid_argument("Unable to parse id: " + str);
124 : }
125 1 : return id;
126 : }
127 :
128 : std::string_view
129 110 : trim(std::string_view s)
130 : {
131 : // NOLINTNEXTLINE(llvm-qualified-auto): MSVC can't convert to const char* from const_iterator
132 220 : auto wsfront = std::find_if_not(s.cbegin(), s.cend(), [](int c) { return std::isspace(c); });
133 110 : if (wsfront == s.cend())
134 0 : return std::string_view {};
135 220 : return std::string_view(&*wsfront,
136 110 : std::find_if_not(s.rbegin(),
137 110 : std::string_view::const_reverse_iterator(wsfront),
138 110 : [](int c) { return std::isspace(c); })
139 110 : .base()
140 110 : - wsfront);
141 : }
142 :
143 : std::vector<unsigned>
144 440 : split_string_to_unsigned(std::string_view str, char delim)
145 : {
146 440 : std::vector<unsigned> output;
147 1728 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
148 1288 : first = second + 1) {
149 1288 : second = std::find(first, last, delim);
150 1288 : if (first != second) {
151 : unsigned result;
152 1286 : auto [p, ec] = std::from_chars(first, second, result);
153 1286 : if (ec == std::errc())
154 1286 : output.emplace_back(result);
155 : }
156 : }
157 440 : return output;
158 0 : }
159 :
160 : void
161 472 : string_replace(std::string& str, const std::string& from, const std::string& to)
162 : {
163 472 : size_t start_pos = 0;
164 582 : while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
165 110 : str.replace(start_pos, from.length(), to);
166 110 : start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
167 : }
168 472 : }
169 :
170 : std::string_view
171 959 : string_remove_suffix(std::string_view str, char separator)
172 : {
173 959 : auto it = str.find(separator);
174 959 : if (it != std::string_view::npos)
175 183 : str = str.substr(0, it);
176 959 : return str;
177 : }
178 :
179 : std::string
180 652 : string_join(const std::set<std::string>& set, std::string_view separator)
181 : {
182 1304 : return fmt::format("{}", fmt::join(set, separator));
183 : }
184 :
185 : std::set<std::string>
186 1588 : string_split_set(std::string& str, std::string_view separator)
187 : {
188 1588 : std::set<std::string> output;
189 1588 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
190 0 : first = second + 1) {
191 0 : second = std::find_first_of(first, last, std::cbegin(separator), std::cend(separator));
192 0 : if (first != second)
193 0 : output.emplace(first, second - first);
194 : }
195 1588 : return output;
196 0 : }
197 :
198 : std::string
199 4 : urlEncode(std::string_view input)
200 : {
201 4 : if (input.empty()) {
202 0 : return {};
203 : }
204 :
205 20 : constexpr auto isAsciiAlnum = [](unsigned char c) {
206 20 : return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
207 : };
208 :
209 4 : std::ostringstream encoded;
210 : // Use uppercase for hex digits
211 4 : encoded << std::uppercase << std::hex;
212 :
213 24 : for (unsigned char c : input) {
214 : // If character is unreserved per RFC 3986, keep it as-is
215 20 : if (isAsciiAlnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
216 16 : encoded << c;
217 : } else {
218 : // Otherwise, percent-encode
219 4 : encoded << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
220 : }
221 : }
222 :
223 4 : return encoded.str();
224 4 : }
225 :
226 : } // namespace jami
|