Line data Source code
1 : /*
2 : ___________________________________________
3 : | _ ___ _ |
4 : | | | |__ \ | | |
5 : | | |__ ) |__ _ __ _ ___ _ __ | |_ |
6 : | | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
7 : | | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
8 : | |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
9 : | __/ | |
10 : | |___/ |
11 : |___________________________________________|
12 :
13 : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
14 : SPDX-License-Identifier: MIT
15 : Copyright (c) 2021 Eduardo Ramos
16 :
17 : Permission is hereby granted, free of charge, to any person obtaining a copy
18 : of this software and associated documentation files (the "Software"), to deal
19 : in the Software without restriction, including without limitation the rights
20 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 : copies of the Software, and to permit persons to whom the Software is
22 : furnished to do so, subject to the following conditions:
23 :
24 : The above copyright notice and this permission notice shall be included in all
25 : copies or substantial portions of the Software.
26 :
27 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 : SOFTWARE.
34 : */
35 :
36 : #include <fstream>
37 : #include <regex>
38 : #include <ctype.h>
39 :
40 : #include <functions.hpp>
41 :
42 : #include <ert/tracing/Logger.hpp>
43 : #include <ert/http2comm/URLFunctions.hpp>
44 :
45 :
46 : namespace h2agent
47 : {
48 : namespace model
49 : {
50 :
51 540 : void calculateStringKey(std::string &key, const std::string &k1, const std::string &k2, const std::string &k3) {
52 540 : key = k1;
53 540 : key += "#";
54 540 : key += k2;
55 540 : if (!k3.empty()) {
56 313 : key += "#";
57 313 : key += k3;
58 : }
59 540 : }
60 :
61 2 : void aggregateKeyPart(std::string &key, const std::string &k1, const std::string &k2) {
62 2 : if (k2.empty()) {
63 1 : key.insert(0, k1 + "#");
64 1 : return;
65 : }
66 1 : key = k1;
67 1 : key += "#";
68 1 : key += k2;
69 : }
70 :
71 53 : bool string2uint64andSign(const std::string &input, std::uint64_t &output, bool &negative) {
72 :
73 53 : bool result = false;
74 :
75 53 : if (!input.empty()) {
76 51 : negative = (input[0] == '-');
77 :
78 : try {
79 63 : output = std::stoull(negative ? input.substr(1):input);
80 39 : result = true;
81 : }
82 12 : catch(std::exception &e)
83 : {
84 12 : std::string msg = ert::tracing::Logger::asString("Error converting string '%s' to unsigned long long integer%s: %s", input.c_str(), (negative ? " with negative sign":""), e.what());
85 12 : ert::tracing::Logger::error(msg, ERT_FILE_LOCATION);
86 12 : }
87 : }
88 :
89 53 : return result;
90 : }
91 :
92 211 : std::map<std::string, std::string> extractQueryParameters(const std::string &queryParams, std::string *sortedQueryParameters, char separator) {
93 211 : std::map<std::string, std::string> result, resultOriginal;
94 :
95 211 : if (queryParams.empty()) return result;
96 :
97 : // Inspired in https://github.com/ben-zen/uri-library
98 : // Loop over the query string looking for '&'s (maybe ';'s), then check each one for
99 : // an '=' to find keys and values; if there's not an '=' then the key
100 : // will have an empty value in the map.
101 211 : size_t pos = 0;
102 211 : size_t qpair_end = queryParams.find_first_of(separator);
103 : do
104 : {
105 317 : std::string qpair = queryParams.substr(pos, ((qpair_end != std::string::npos) ? (qpair_end - pos) : std::string::npos));
106 317 : size_t key_value_divider = qpair.find_first_of('=');
107 317 : std::string key = qpair.substr(0, key_value_divider);
108 317 : std::string value;
109 317 : if (key_value_divider != std::string::npos)
110 : {
111 317 : value = qpair.substr((key_value_divider + 1));
112 : }
113 :
114 317 : if (result.count(key) != 0)
115 : {
116 1 : ert::tracing::Logger::error("Cannot normalize URI query parameters: repeated key found", ERT_FILE_LOCATION);
117 1 : result.clear();
118 1 : return result;
119 : }
120 :
121 : // Store original value when sorted query parameters list is requested:
122 316 : if (sortedQueryParameters) {
123 15 : resultOriginal.emplace(key, value);
124 : }
125 :
126 316 : std::string valueDecoded = ert::http2comm::URLFunctions::decode(value);
127 316 : bool decoded = (valueDecoded != value);
128 316 : if (decoded) {
129 29 : value = valueDecoded;
130 : }
131 316 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Extracted query parameter %s = %s%s", key.c_str(), value.c_str(), (decoded ? " (decoded)":"")), ERT_FILE_LOCATION));
132 316 : result.emplace(key, value);
133 316 : pos = ((qpair_end != std::string::npos) ? (qpair_end + 1) : std::string::npos);
134 316 : qpair_end = queryParams.find_first_of(separator, pos);
135 319 : }
136 316 : while ((qpair_end != std::string::npos) || (pos != std::string::npos));
137 :
138 : // Build sorted literal:
139 210 : if (sortedQueryParameters) {
140 12 : std::string &ref = *sortedQueryParameters;
141 12 : ref.clear();
142 12 : ref.reserve(queryParams.size());
143 27 : for(auto it = resultOriginal.begin(); it != resultOriginal.end(); it ++) {
144 15 : if (it != resultOriginal.begin()) ref += separator;
145 15 : ref += it->first; // key
146 15 : if (!it->second.empty()) {
147 15 : ref += "=";
148 15 : ref += it->second;
149 : }
150 : }
151 : }
152 :
153 210 : return result;
154 : } // LCOV_EXCL_LINE
155 :
156 2 : bool getFileContent(const std::string &filePath, std::string &content)
157 : {
158 2 : std::ifstream ifs(filePath);
159 :
160 2 : if (!ifs.is_open()) {
161 1 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot open file '%s' !", filePath.c_str()), ERT_FILE_LOCATION);
162 1 : return false;
163 : }
164 :
165 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Reading content from file '%s'", filePath.c_str()), ERT_FILE_LOCATION));
166 1 : std::stringstream buffer;
167 1 : buffer << ifs.rdbuf();
168 1 : ifs.close();
169 1 : content = buffer.str();
170 :
171 1 : return true;
172 2 : }
173 :
174 352 : bool parseJsonContent(const std::string &content, nlohmann::json &jsonObject, bool writeException) {
175 :
176 : try {
177 355 : jsonObject = nlohmann::json::parse(content);
178 349 : LOGDEBUG(
179 : std::string msg("Json body parsed: ");
180 : msg += jsonObject.dump();
181 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
182 : );
183 : }
184 3 : catch (nlohmann::json::parse_error& e)
185 : {
186 3 : std::stringstream ss;
187 3 : ss << "Json content parse error: " << e.what()
188 3 : << " | exception id: " << e.id
189 3 : << " | byte position of error: " << e.byte;
190 :
191 3 : if (writeException)
192 2 : jsonObject = ss.str();
193 :
194 : // This will be debug, not error, because plain strings would always fail and some application
195 : // could work with non-json bodies:
196 3 : LOGDEBUG(ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION));
197 :
198 3 : return false;
199 3 : }
200 :
201 349 : return true;
202 : }
203 :
204 4 : bool asAsciiString(const std::string &input, std::string &output) {
205 :
206 4 : bool result = true; // supposed printable by default
207 :
208 4 : if(input.empty()) {
209 1 : output = "<null>";
210 1 : return false;
211 : }
212 :
213 3 : std::for_each(input.begin(), input.end(), [&] (char const &c) {
214 :
215 19 : int printable = isprint(c);
216 19 : output += (printable ? c:'.');
217 :
218 19 : if(!printable) result = false;
219 19 : });
220 :
221 3 : return result;
222 : }
223 :
224 117 : bool asHexString(const std::string &input, std::string &output) {
225 :
226 117 : bool result = true; // supposed printable by default
227 :
228 : int byte;
229 117 : output = "0x";
230 :
231 117 : std::for_each(input.begin(), input.end(), [&] (char const &c) {
232 :
233 1130 : byte = (c & 0xf0) >> 4;
234 1130 : output += (byte >= 0 && byte <= 9) ? (byte + '0') : ((byte - 0xa) + 'a');
235 1130 : byte = (c & 0x0f);
236 1130 : output += (byte >= 0 && byte <= 9) ? (byte + '0') : ((byte - 0xa) + 'a');
237 :
238 1130 : if (!isprint(c)) result = false;
239 1130 : });
240 :
241 117 : return result;
242 : }
243 :
244 23 : bool fromHexString(const std::string &input, std::string &output) {
245 :
246 23 : bool result = true; // supposed successful by default
247 :
248 23 : bool has0x = (input.rfind("0x", 0) == 0);
249 :
250 23 : if((input.length() % 2) != 0) {
251 1 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string due to odd length (%d): %s", input.length(), input.c_str()), ERT_FILE_LOCATION));
252 1 : return false;
253 : }
254 :
255 22 : output = "";
256 22 : const char* src = input.data(); // fastest that accessing input[ii]
257 : unsigned char hex;
258 : int aux;
259 :
260 426 : for(int ii = 1 + (has0x ? 2:0), maxii = input.length(); ii < maxii; ii += 2) {
261 406 : if(isxdigit(aux = src[ii-1]) == 0) {
262 1 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string: %s", input.c_str()), ERT_FILE_LOCATION));
263 1 : return false;
264 : }
265 :
266 405 : hex = ((aux >= '0' && aux <= '9') ? (aux - '0') : ((aux - 'a') + 0x0a)) << 4;
267 :
268 405 : if(isxdigit(aux = src[ii]) == 0) {
269 1 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string: %s", input.c_str()), ERT_FILE_LOCATION));
270 1 : return false;
271 : }
272 :
273 404 : hex |= (aux >= '0' && aux <= '9') ? (aux - '0') : ((aux - 'a') + 0x0a);
274 404 : output += hex;
275 : }
276 :
277 20 : return result;
278 : }
279 :
280 8 : bool jsonConstraint(const nlohmann::json &received, const nlohmann::json &expected, std::string &failReport) {
281 :
282 8 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Received object: %s", received.dump().c_str()), ERT_FILE_LOCATION));
283 8 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Expected object: %s", expected.dump().c_str()), ERT_FILE_LOCATION));
284 :
285 14 : for (auto& [key, value] : expected.items()) {
286 :
287 : // Check if key exists in document:
288 11 : if (!received.contains(key)) {
289 1 : failReport = ert::tracing::Logger::asString("JsonConstraint FAILED: expected key '%s' is missing in validated source", key.c_str());
290 1 : LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
291 1 : return false;
292 : }
293 :
294 : // Check if value is JSON object to make recursive call:
295 10 : if (value.is_object()) {
296 2 : if (!h2agent::model::jsonConstraint(received[key], value, failReport)) {
297 1 : return false;
298 : }
299 : } else {
300 : // Check same value:
301 8 : if (received[key] != value) {
302 3 : failReport = ert::tracing::Logger::asString("JsonConstraint FAILED: expected value for key '%s' differs regarding validated source", key.c_str());
303 3 : LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
304 3 : return false;
305 : }
306 : }
307 13 : }
308 :
309 3 : LOGDEBUG(ert::tracing::Logger::debug("JsonConstraint SUCCEED", ERT_FILE_LOCATION));
310 3 : return true;
311 : }
312 :
313 7 : std::string fixMetricsName(const std::string &in) {
314 :
315 7 : std::string result{}; // = std::regex_replace(key_, invalidMetricsNamesCharactersRegex, "_");
316 :
317 : // https://prometheus.io/docs/instrumenting/writing_exporters/#naming
318 7 : static std::regex validMetricsNamesCharactersRegex("[a-zA-Z0-9:_]", std::regex::optimize);
319 :
320 79 : for (char c : in) {
321 144 : if (std::regex_match(std::string(1, c), validMetricsNamesCharactersRegex)) {
322 68 : result += c;
323 : } else {
324 4 : result += "_";
325 : }
326 : }
327 :
328 7 : return result;
329 0 : }
330 :
331 : }
332 : }
333 :
|