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