Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 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 :
26 : #include <sstream>
27 : #include <cctype>
28 : #include <algorithm>
29 : #include <ostream>
30 : #include <iomanip>
31 : #include <stdexcept>
32 : #include <ios>
33 : #include <charconv>
34 : #include <string_view>
35 : #ifdef _WIN32
36 : #include <windows.h>
37 : #include <oleauto.h>
38 : #endif
39 :
40 : #include <ciso646> // fix windows compiler bug
41 :
42 : #include "json_utils.h"
43 :
44 : namespace jami {
45 :
46 : namespace json {
47 : Json::CharReaderBuilder
48 40 : getJsonReaderBuilder()
49 : {
50 40 : Json::CharReaderBuilder builder;
51 40 : builder["collectComments"] = false;
52 40 : return builder;
53 0 : }
54 :
55 : Json::StreamWriterBuilder
56 40 : getJsonWriterBuilder()
57 : {
58 40 : Json::StreamWriterBuilder builder;
59 40 : builder["commentStyle"] = "None";
60 40 : builder["indentation"] = "";
61 40 : return builder;
62 0 : }
63 :
64 : const Json::CharReaderBuilder rbuilder = getJsonReaderBuilder();
65 : const Json::StreamWriterBuilder wbuilder = getJsonWriterBuilder();
66 : } // namespace json
67 :
68 : const std::string&
69 691 : userAgent()
70 : {
71 714 : static const std::string USER_AGENT = fmt::format("{:s} ({:s}/{:s})", PACKAGE_NAME, platform(), arch());
72 691 : return USER_AGENT;
73 : }
74 :
75 : #ifdef _WIN32
76 : std::wstring
77 : to_wstring(const std::string& str, int codePage)
78 : {
79 : int srcLength = (int) str.length();
80 : int requiredSize = MultiByteToWideChar(codePage, 0, str.c_str(), srcLength, nullptr, 0);
81 : if (!requiredSize) {
82 : throw std::runtime_error("Unable to convert string to wstring");
83 : }
84 : std::wstring result((size_t) requiredSize, 0);
85 : if (!MultiByteToWideChar(codePage, 0, str.c_str(), srcLength, &(*result.begin()), requiredSize)) {
86 : throw std::runtime_error("Unable to convert string to wstring");
87 : }
88 : return result;
89 : }
90 :
91 : std::string
92 : to_string(const std::wstring& wstr, int codePage)
93 : {
94 : int srcLength = (int) wstr.length();
95 : int requiredSize = WideCharToMultiByte(codePage, 0, wstr.c_str(), srcLength, nullptr, 0, 0, 0);
96 : if (!requiredSize) {
97 : throw std::runtime_error("Unable to convert wstring to string");
98 : }
99 : std::string result((size_t) requiredSize, 0);
100 : if (!WideCharToMultiByte(codePage, 0, wstr.c_str(), srcLength, &(*result.begin()), requiredSize, 0, 0)) {
101 : throw std::runtime_error("Unable to convert wstring to string");
102 : }
103 : return result;
104 : }
105 : #endif
106 :
107 : std::string
108 67 : to_string(double value)
109 : {
110 : char buf[64];
111 67 : int len = snprintf(buf, sizeof(buf), "%-.*G", 16, value);
112 67 : if (len <= 0)
113 0 : throw std::invalid_argument {"Unable to parse double"};
114 67 : return {buf, (size_t) len};
115 : }
116 :
117 : std::string
118 795 : to_hex_string(uint64_t id)
119 : {
120 795 : return fmt::format("{:016x}", id);
121 : }
122 :
123 : uint64_t
124 1 : from_hex_string(const std::string& str)
125 : {
126 : uint64_t id;
127 1 : if (auto [p, ec] = std::from_chars(str.data(), str.data() + str.size(), id, 16); ec != std::errc()) {
128 0 : throw std::invalid_argument("Unable to parse id: " + str);
129 : }
130 1 : return id;
131 : }
132 :
133 : std::string_view
134 111 : trim(std::string_view s)
135 : {
136 222 : auto wsfront = std::find_if_not(s.cbegin(), s.cend(), [](int c) { return std::isspace(c); });
137 111 : if (wsfront == s.cend())
138 0 : return std::string_view {};
139 222 : return std::string_view(&*wsfront,
140 111 : std::find_if_not(s.rbegin(),
141 111 : std::string_view::const_reverse_iterator(wsfront),
142 111 : [](int c) { return std::isspace(c); })
143 111 : .base()
144 111 : - wsfront);
145 : }
146 :
147 : std::vector<unsigned>
148 442 : split_string_to_unsigned(std::string_view str, char delim)
149 : {
150 442 : std::vector<unsigned> output;
151 1736 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
152 1294 : first = second + 1) {
153 1294 : second = std::find(first, last, delim);
154 1294 : if (first != second) {
155 : unsigned result;
156 1292 : auto [p, ec] = std::from_chars(first, second, result);
157 1292 : if (ec == std::errc())
158 1292 : output.emplace_back(result);
159 : }
160 : }
161 442 : return output;
162 0 : }
163 :
164 : void
165 474 : string_replace(std::string& str, const std::string& from, const std::string& to)
166 : {
167 474 : size_t start_pos = 0;
168 582 : while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
169 108 : str.replace(start_pos, from.length(), to);
170 108 : start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
171 : }
172 474 : }
173 :
174 : std::string_view
175 959 : string_remove_suffix(std::string_view str, char separator)
176 : {
177 959 : auto it = str.find(separator);
178 959 : if (it != std::string_view::npos)
179 174 : str = str.substr(0, it);
180 959 : return str;
181 : }
182 :
183 : std::string
184 650 : string_join(const std::set<std::string>& set, std::string_view separator)
185 : {
186 1300 : return fmt::format("{}", fmt::join(set, separator));
187 : }
188 :
189 : std::set<std::string>
190 1588 : string_split_set(std::string& str, std::string_view separator)
191 : {
192 1588 : std::set<std::string> output;
193 1588 : for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last;
194 0 : first = second + 1) {
195 0 : second = std::find_first_of(first, last, std::cbegin(separator), std::cend(separator));
196 0 : if (first != second)
197 0 : output.emplace(first, second - first);
198 : }
199 1588 : return output;
200 0 : }
201 :
202 : std::string
203 4 : urlEncode(std::string_view input)
204 : {
205 4 : if (input.empty()) {
206 0 : return {};
207 : }
208 :
209 20 : auto isAsciiAlnum = [](unsigned char c) {
210 20 : return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
211 : };
212 :
213 4 : std::ostringstream encoded;
214 : // Use uppercase for hex digits
215 4 : encoded << std::uppercase << std::hex;
216 :
217 24 : for (unsigned char c : input) {
218 : // If character is unreserved per RFC 3986, keep it as-is
219 20 : if (isAsciiAlnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
220 16 : encoded << c;
221 : } else {
222 : // Otherwise, percent-encode
223 4 : encoded << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
224 : }
225 : }
226 :
227 4 : return encoded.str();
228 4 : }
229 :
230 : } // namespace jami
|