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 : Json::CharReaderBuilder
47 40 : getJsonReaderBuilder()
48 : {
49 40 : Json::CharReaderBuilder builder;
50 40 : builder["collectComments"] = false;
51 40 : return builder;
52 0 : }
53 :
54 : 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 690 : userAgent()
69 : {
70 713 : static const std::string USER_AGENT = fmt::format("{:s} ({:s}/{:s})", PACKAGE_NAME, platform(), arch());
71 690 : 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 811 : to_hex_string(uint64_t id)
114 : {
115 3244 : 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 106 : trim(std::string_view s)
130 : {
131 212 : auto wsfront = std::find_if_not(s.cbegin(), s.cend(), [](int c) { return std::isspace(c); });
132 106 : if (wsfront == s.cend())
133 0 : return std::string_view {};
134 212 : return std::string_view(&*wsfront,
135 106 : std::find_if_not(s.rbegin(),
136 106 : std::string_view::const_reverse_iterator(wsfront),
137 106 : [](int c) { return std::isspace(c); })
138 106 : .base()
139 106 : - wsfront);
140 : }
141 :
142 : std::vector<unsigned>
143 425 : split_string_to_unsigned(std::string_view str, char delim)
144 : {
145 425 : std::vector<unsigned> output;
146 1668 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
147 1243 : first = second + 1) {
148 1243 : second = std::find(first, last, delim);
149 1243 : if (first != second) {
150 : unsigned result;
151 1241 : auto [p, ec] = std::from_chars(first, second, result);
152 1241 : if (ec == std::errc())
153 1241 : output.emplace_back(result);
154 : }
155 : }
156 425 : return output;
157 0 : }
158 :
159 : void
160 465 : string_replace(std::string& str, const std::string& from, const std::string& to)
161 : {
162 465 : size_t start_pos = 0;
163 574 : while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
164 109 : str.replace(start_pos, from.length(), to);
165 109 : start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
166 : }
167 465 : }
168 :
169 : std::string_view
170 961 : string_remove_suffix(std::string_view str, char separator)
171 : {
172 961 : auto it = str.find(separator);
173 961 : if (it != std::string_view::npos)
174 173 : str = str.substr(0, it);
175 961 : return str;
176 : }
177 :
178 : std::string
179 621 : string_join(const std::set<std::string>& set, std::string_view separator)
180 : {
181 1242 : return fmt::format("{}", fmt::join(set, separator));
182 : }
183 :
184 : std::set<std::string>
185 1572 : string_split_set(std::string& str, std::string_view separator)
186 : {
187 1572 : std::set<std::string> output;
188 1572 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
189 0 : first = second + 1) {
190 0 : second = std::find_first_of(first, last, std::cbegin(separator), std::cend(separator));
191 0 : if (first != second)
192 0 : output.emplace(first, second - first);
193 : }
194 1572 : return output;
195 0 : }
196 :
197 : std::string
198 4 : urlEncode(std::string_view input)
199 : {
200 4 : if (input.empty()) {
201 0 : return {};
202 : }
203 :
204 20 : constexpr auto isAsciiAlnum = [](unsigned char c) {
205 20 : return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
206 : };
207 :
208 4 : std::ostringstream encoded;
209 : // Use uppercase for hex digits
210 4 : encoded << std::uppercase << std::hex;
211 :
212 24 : for (unsigned char c : input) {
213 : // If character is unreserved per RFC 3986, keep it as-is
214 20 : if (isAsciiAlnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
215 16 : encoded << c;
216 : } else {
217 : // Otherwise, percent-encode
218 4 : encoded << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
219 : }
220 : }
221 :
222 4 : return encoded.str();
223 4 : }
224 :
225 : } // namespace jami
|