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 40 : getJsonReaderBuilder()
48 : {
49 40 : Json::CharReaderBuilder builder;
50 40 : builder["collectComments"] = false;
51 40 : return builder;
52 0 : }
53 :
54 : static Json::StreamWriterBuilder
55 40 : getJsonWriterBuilder()
56 : {
57 40 : Json::StreamWriterBuilder builder;
58 40 : builder["commentStyle"] = "None";
59 40 : builder["indentation"] = "";
60 40 : 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 600 : userAgent()
69 : {
70 622 : static const std::string USER_AGENT = fmt::format("{:s} ({:s}/{:s})", PACKAGE_NAME, platform(), arch());
71 600 : 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 65 : to_string(double value)
108 : {
109 260 : return fmt::format(FMT_COMPILE("{:.16G}"), value);
110 : }
111 :
112 : std::string
113 733 : to_hex_string(uint64_t id)
114 : {
115 2932 : 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 53 : trim(std::string_view s)
130 : {
131 : // NOLINTNEXTLINE(llvm-qualified-auto): MSVC can't convert to const char* from const_iterator
132 106 : auto wsfront = std::find_if_not(s.cbegin(), s.cend(), [](int c) { return std::isspace(c); });
133 53 : if (wsfront == s.cend())
134 0 : return std::string_view {};
135 106 : return std::string_view(&*wsfront,
136 53 : std::find_if_not(s.rbegin(),
137 53 : std::string_view::const_reverse_iterator(wsfront),
138 53 : [](int c) { return std::isspace(c); })
139 53 : .base()
140 53 : - wsfront);
141 : }
142 :
143 : std::vector<unsigned>
144 298 : split_string_to_unsigned(std::string_view str, char delim)
145 : {
146 298 : std::vector<unsigned> output;
147 1183 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
148 885 : first = second + 1) {
149 885 : second = std::find(first, last, delim);
150 885 : if (first != second) {
151 : unsigned result;
152 883 : auto [p, ec] = std::from_chars(first, second, result);
153 883 : if (ec == std::errc())
154 883 : output.emplace_back(result);
155 : }
156 : }
157 298 : return output;
158 0 : }
159 :
160 : void
161 152 : string_replace(std::string& str, const std::string& from, const std::string& to)
162 : {
163 152 : size_t start_pos = 0;
164 170 : while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
165 18 : str.replace(start_pos, from.length(), to);
166 18 : start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
167 : }
168 152 : }
169 :
170 : std::string_view
171 15 : string_remove_suffix(std::string_view str, char separator)
172 : {
173 15 : auto it = str.find(separator);
174 15 : if (it != std::string_view::npos)
175 5 : str = str.substr(0, it);
176 15 : return str;
177 : }
178 :
179 : std::string
180 629 : string_join(const std::set<std::string>& set, std::string_view separator)
181 : {
182 1258 : return fmt::format("{}", fmt::join(set, separator));
183 : }
184 :
185 : std::set<std::string>
186 1404 : string_split_set(std::string& str, std::string_view separator)
187 : {
188 1404 : std::set<std::string> output;
189 1404 : 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 1404 : 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
|