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 <sstream>
37 : #include <chrono>
38 : #include <sys/time.h>
39 : #include <ctime>
40 : #include <time.h> /* time_t, struct tm, time, localtime, strftime */
41 : #include <string>
42 : #include <algorithm>
43 : //#include <fcntl.h> // non-blocking fgets call
44 :
45 : #include <nlohmann/json.hpp>
46 : #include <arashpartow/exprtk.hpp>
47 :
48 : #include <ert/tracing/Logger.hpp>
49 : #include <ert/http2comm/Http.hpp>
50 :
51 : #include <AdminServerProvision.hpp>
52 : #include <MockServerData.hpp>
53 : #include <MockClientData.hpp>
54 : #include <Configuration.hpp>
55 : #include <GlobalVariable.hpp>
56 : #include <FileManager.hpp>
57 : #include <SocketManager.hpp>
58 : #include <AdminData.hpp>
59 :
60 : #include <functions.hpp>
61 :
62 :
63 : typedef exprtk::expression<double> expression_t;
64 : typedef exprtk::parser<double> parser_t;
65 :
66 : namespace h2agent
67 : {
68 : namespace model
69 : {
70 :
71 246 : AdminServerProvision::AdminServerProvision() : in_state_(DEFAULT_ADMIN_PROVISION_STATE),
72 246 : out_state_(DEFAULT_ADMIN_PROVISION_STATE),
73 369 : response_delay_ms_(0), mock_server_events_data_(nullptr), mock_client_events_data_(nullptr) {;}
74 :
75 :
76 213 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getRequestSchema() {
77 :
78 213 : if(request_schema_id_.empty()) return nullptr;
79 :
80 7 : if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
81 6 : if (request_schema_) return request_schema_; // provision cache
82 2 : request_schema_ = admin_data_->getSchemaData().find(request_schema_id_);
83 : }
84 :
85 3 : LOGWARNING(
86 : if (!request_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for incoming message: VALIDATION will be IGNORED", request_schema_id_.c_str()), ERT_FILE_LOCATION);
87 : );
88 :
89 3 : return request_schema_;
90 : }
91 :
92 106 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getResponseSchema() {
93 :
94 106 : if(response_schema_id_.empty()) return nullptr;
95 :
96 3 : if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
97 2 : if (response_schema_) return response_schema_; // provision cache
98 1 : response_schema_ = admin_data_->getSchemaData().find(response_schema_id_);
99 : }
100 :
101 2 : LOGWARNING(
102 : if (!response_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for outgoing message: VALIDATION will be IGNORED", response_schema_id_.c_str()), ERT_FILE_LOCATION);
103 : );
104 :
105 2 : return response_schema_;
106 : }
107 :
108 136 : bool AdminServerProvision::processSources(std::shared_ptr<Transformation> transformation,
109 : TypeConverter& sourceVault,
110 : std::map<std::string, std::string>& variables, /* Command generates "rc" */
111 : const std::string &requestUri,
112 : const std::string &requestUriPath,
113 : const std::map<std::string, std::string> &requestQueryParametersMap,
114 : const DataPart &requestBodyDataPart,
115 : const nghttp2::asio_http2::header_map &requestHeaders,
116 : bool &eraser,
117 : std::uint64_t generalUniqueServerSequence,
118 : bool usesResponseBodyAsTransformationJsonTarget, const nlohmann::json &responseBodyJson) const {
119 :
120 136 : switch (transformation->getSourceType()) {
121 4 : case Transformation::SourceType::RequestUri:
122 : {
123 4 : sourceVault.setString(requestUri);
124 4 : break;
125 : }
126 2 : case Transformation::SourceType::RequestUriPath:
127 : {
128 2 : sourceVault.setString(requestUriPath);
129 2 : break;
130 : }
131 2 : case Transformation::SourceType::RequestUriParam:
132 : {
133 2 : auto iter = requestQueryParametersMap.find(transformation->getSource());
134 2 : if (iter != requestQueryParametersMap.end()) sourceVault.setString(iter->second);
135 : else {
136 1 : LOGDEBUG(
137 : std::string msg = ert::tracing::Logger::asString("Unable to extract query parameter '%s' in transformation item", transformation->getSource().c_str());
138 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
139 : );
140 1 : return false;
141 : }
142 1 : break;
143 : }
144 16 : case Transformation::SourceType::RequestBody:
145 : {
146 16 : if (requestBodyDataPart.isJson()) {
147 13 : std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
148 13 : replaceVariables(path, transformation->getSourcePatterns(), variables, global_variable_);
149 13 : if (!sourceVault.setObject(requestBodyDataPart.getJson(), path)) {
150 1 : LOGDEBUG(
151 : std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from request body (it is null) in transformation item", transformation->getSource().c_str());
152 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
153 : );
154 1 : return false;
155 : }
156 13 : }
157 : else {
158 3 : sourceVault.setString(requestBodyDataPart.str());
159 : }
160 15 : break;
161 : }
162 3 : case Transformation::SourceType::ResponseBody:
163 : {
164 3 : std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
165 3 : replaceVariables(path, transformation->getSourcePatterns(), variables, global_variable_);
166 3 : if (!sourceVault.setObject(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), path)) {
167 1 : LOGDEBUG(
168 : std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from response body (it is null) in transformation item", transformation->getSource().c_str());
169 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
170 : );
171 1 : return false;
172 : }
173 2 : break;
174 3 : }
175 2 : case Transformation::SourceType::RequestHeader:
176 : {
177 2 : auto iter = requestHeaders.find(transformation->getSource());
178 2 : if (iter != requestHeaders.end()) sourceVault.setString(iter->second.value);
179 : else {
180 1 : LOGDEBUG(
181 : std::string msg = ert::tracing::Logger::asString("Unable to extract request header '%s' in transformation item", transformation->getSource().c_str());
182 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
183 : );
184 1 : return false;
185 : }
186 1 : break;
187 : }
188 8 : case Transformation::SourceType::Eraser:
189 : {
190 8 : sourceVault.setString(""); // with other than response body nodes, it acts like setting empty string
191 8 : eraser = true;
192 8 : break;
193 : }
194 3 : case Transformation::SourceType::Math:
195 : {
196 3 : std::string expressionString = transformation->getSource();
197 3 : replaceVariables(expressionString, transformation->getSourcePatterns(), variables, global_variable_);
198 :
199 : /*
200 : We don't use builtin variables as we can parse h2agent ones which is easier to implement:
201 :
202 : typedef exprtk::symbol_table<double> symbol_table_t;
203 : symbol_table_t symbol_table;
204 : double x = 2.0;
205 : symbol_table.add_variable("x",x);
206 : expression.register_symbol_table(symbol_table);
207 : parser.compile("3*x",expression);
208 : std::cout << expression.value() << std::endl; // 3*2
209 : */
210 :
211 3 : expression_t expression;
212 3 : parser_t parser;
213 3 : parser.compile(expressionString, expression);
214 :
215 3 : double result = expression.value(); // if the result has decimals, set as float. If not, set as integer:
216 3 : if (result == (int)result) sourceVault.setInteger(expression.value());
217 1 : else sourceVault.setFloat(expression.value());
218 3 : break;
219 3 : }
220 2 : case Transformation::SourceType::Random:
221 : {
222 2 : int range = transformation->getSourceI2() - transformation->getSourceI1() + 1;
223 2 : sourceVault.setInteger(transformation->getSourceI1() + (rand() % range));
224 2 : break;
225 : }
226 1 : case Transformation::SourceType::RandomSet:
227 : {
228 1 : sourceVault.setStringReplacingVariables(transformation->getSourceTokenized()[rand () % transformation->getSourceTokenized().size()], transformation->getSourcePatterns(), variables, global_variable_); // replace variables if they exist
229 1 : break;
230 : }
231 4 : case Transformation::SourceType::Timestamp:
232 : {
233 4 : if (transformation->getSource() == "s") {
234 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
235 : }
236 3 : else if (transformation->getSource() == "ms") {
237 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
238 : }
239 2 : else if (transformation->getSource() == "us") {
240 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
241 : }
242 1 : else if (transformation->getSource() == "ns") {
243 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
244 : }
245 4 : break;
246 : }
247 1 : case Transformation::SourceType::Strftime:
248 : {
249 1 : std::time_t unixTime = 0;
250 1 : std::time (&unixTime);
251 1 : char buffer[100] = {0};
252 1 : /*size_t size = */strftime(buffer, sizeof(buffer), transformation->getSource().c_str(), localtime(&unixTime));
253 : //if (size > 1) { // convert TZ offset to RFC3339 format
254 : // char minute[] = { buffer[size-2], buffer[size-1], '\0' };
255 : // sprintf(buffer + size - 2, ":%s", minute);
256 : //}
257 :
258 2 : sourceVault.setStringReplacingVariables(std::string(buffer), transformation->getSourcePatterns(), variables, global_variable_); // replace variables if they exist
259 1 : break;
260 : }
261 2 : case Transformation::SourceType::Recvseq:
262 : {
263 2 : sourceVault.setUnsigned(generalUniqueServerSequence);
264 2 : break;
265 : }
266 16 : case Transformation::SourceType::SVar:
267 : {
268 16 : std::string varname = transformation->getSource();
269 16 : replaceVariables(varname, transformation->getSourcePatterns(), variables, global_variable_);
270 16 : auto iter = variables.find(varname);
271 16 : if (iter != variables.end()) sourceVault.setString(iter->second);
272 : else {
273 5 : LOGDEBUG(
274 : std::string msg = ert::tracing::Logger::asString("Unable to extract source variable '%s' in transformation item", varname.c_str());
275 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
276 : );
277 5 : return false;
278 : }
279 11 : break;
280 16 : }
281 9 : case Transformation::SourceType::SGVar:
282 : {
283 9 : std::string varname = transformation->getSource();
284 9 : replaceVariables(varname, transformation->getSourcePatterns(), variables, global_variable_);
285 9 : std::string globalVariableValue{};
286 9 : bool exists = global_variable_->tryGet(varname, globalVariableValue);
287 9 : if (exists) sourceVault.setString(globalVariableValue);
288 : else {
289 2 : LOGDEBUG(
290 : std::string msg = ert::tracing::Logger::asString("Unable to extract source global variable '%s' in transformation item", varname.c_str());
291 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
292 : );
293 2 : return false;
294 : }
295 7 : break;
296 18 : }
297 46 : case Transformation::SourceType::Value:
298 : {
299 46 : sourceVault.setStringReplacingVariables(transformation->getSource(), transformation->getSourcePatterns(), variables, global_variable_); // replace variables if they exist
300 46 : break;
301 : }
302 3 : case Transformation::SourceType::ServerEvent:
303 : {
304 : // transformation->getSourceTokenized() is a vector:
305 : //
306 : // requestMethod: index 0
307 : // requestUri: index 1
308 : // eventNumber: index 2
309 : // eventPath: index 3
310 3 : std::string event_method = transformation->getSourceTokenized()[0];
311 3 : replaceVariables(event_method, transformation->getSourcePatterns(), variables, global_variable_);
312 3 : std::string event_uri = transformation->getSourceTokenized()[1];
313 3 : replaceVariables(event_uri, transformation->getSourcePatterns(), variables, global_variable_);
314 3 : std::string event_number = transformation->getSourceTokenized()[2];
315 3 : replaceVariables(event_number, transformation->getSourcePatterns(), variables, global_variable_);
316 3 : std::string event_path = transformation->getSourceTokenized()[3];
317 3 : replaceVariables(event_path, transformation->getSourcePatterns(), variables, global_variable_);
318 :
319 : // Now, access the server data for the former selection values:
320 3 : nlohmann::json object;
321 3 : EventKey ekey(event_method, event_uri, event_number);
322 3 : auto mockServerRequest = mock_server_events_data_->getEvent(ekey);
323 3 : if (!mockServerRequest) {
324 1 : LOGDEBUG(
325 : std::string msg = ert::tracing::Logger::asString("Unable to extract server event for variable '%s' in transformation item", transformation->getSource().c_str());
326 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
327 : );
328 1 : return false;
329 : }
330 :
331 6 : if (!sourceVault.setObject(mockServerRequest->getJson(), event_path /* document path (empty or not to be whole 'requests number' or node) */)) {
332 1 : LOGDEBUG(
333 : std::string msg = ert::tracing::Logger::asString("Unexpected error extracting server event for variable '%s' in transformation item", transformation->getSource().c_str());
334 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
335 : );
336 1 : return false;
337 : }
338 :
339 1 : LOGDEBUG(
340 : std::string msg = ert::tracing::Logger::asString("Extracted object from server event: %s", sourceVault.asString().c_str());
341 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
342 : );
343 1 : break;
344 21 : }
345 3 : case Transformation::SourceType::InState:
346 : {
347 3 : sourceVault.setString(getInState());
348 3 : break;
349 : }
350 2 : case Transformation::SourceType::STxtFile:
351 : {
352 2 : std::string path = transformation->getSource();
353 2 : replaceVariables(path, transformation->getSourcePatterns(), variables, global_variable_);
354 :
355 2 : std::string content;
356 2 : file_manager_->read(path, content, true/*text*/);
357 2 : sourceVault.setString(std::move(content));
358 2 : break;
359 2 : }
360 2 : case Transformation::SourceType::SBinFile:
361 : {
362 2 : std::string path = transformation->getSource();
363 2 : replaceVariables(path, transformation->getSourcePatterns(), variables, global_variable_);
364 :
365 2 : std::string content;
366 2 : file_manager_->read(path, content, false/*binary*/);
367 2 : sourceVault.setString(std::move(content));
368 2 : break;
369 2 : }
370 2 : case Transformation::SourceType::Command:
371 : {
372 2 : std::string command = transformation->getSource();
373 2 : replaceVariables(command, transformation->getSourcePatterns(), variables, global_variable_);
374 :
375 : static char buffer[256];
376 2 : std::string output{};
377 :
378 2 : FILE *fp = popen(command.c_str(), "r");
379 2 : variables["rc"] = "-1"; // rare case where fp could be NULL
380 2 : if (fp) {
381 : /* This makes asyncronous the command execution, but we will have broken pipe and cannot capture anything.
382 : // fgets is blocking (https://stackoverflow.com/questions/6055702/using-fgets-as-non-blocking-function-c/6055774#6055774)
383 : int fd = fileno(fp);
384 : int flags = fcntl(fd, F_GETFL, 0);
385 : flags |= O_NONBLOCK;
386 : fcntl(fd, F_SETFL, flags);
387 : */
388 :
389 3 : while(fgets(buffer, sizeof(buffer), fp))
390 : {
391 1 : output += buffer;
392 : }
393 6 : variables["rc"] = std::to_string(WEXITSTATUS(/* status = */pclose(fp))); // rc = status >>= 8; // divide by 256
394 : }
395 :
396 2 : sourceVault.setString(std::move(output));
397 2 : break;
398 2 : }
399 3 : case Transformation::SourceType::ClientEvent:
400 : {
401 : // transformation->getSourceTokenized() is a vector:
402 : //
403 : // clientEndpointId: index 0
404 : // requestMethod: index 1
405 : // requestUri: index 2
406 : // eventNumber: index 3
407 : // eventPath: index 4
408 3 : std::string event_endpoint = transformation->getSourceTokenized()[0];
409 3 : replaceVariables(event_endpoint, transformation->getSourcePatterns(), variables, global_variable_);
410 3 : std::string event_method = transformation->getSourceTokenized()[1];
411 3 : replaceVariables(event_method, transformation->getSourcePatterns(), variables, global_variable_);
412 3 : std::string event_uri = transformation->getSourceTokenized()[2];
413 3 : replaceVariables(event_uri, transformation->getSourcePatterns(), variables, global_variable_);
414 3 : std::string event_number = transformation->getSourceTokenized()[3];
415 3 : replaceVariables(event_number, transformation->getSourcePatterns(), variables, global_variable_);
416 3 : std::string event_path = transformation->getSourceTokenized()[4];
417 3 : replaceVariables(event_path, transformation->getSourcePatterns(), variables, global_variable_);
418 :
419 3 : DataKey dkey(event_endpoint, event_method, event_uri);
420 3 : EventKey ekey(dkey, event_number);
421 3 : auto mockClientRequest = mock_client_events_data_->getEvent(ekey);
422 3 : if (!mockClientRequest) {
423 1 : LOGDEBUG(
424 : std::string msg = ert::tracing::Logger::asString("Unable to extract client event for variable '%s' in transformation item", transformation->getSource().c_str());
425 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
426 : );
427 1 : return false;
428 : }
429 :
430 6 : if (!sourceVault.setObject(mockClientRequest->getJson(), event_path)) {
431 1 : LOGDEBUG(
432 : std::string msg = ert::tracing::Logger::asString("Unexpected error extracting client event for variable '%s' in transformation item", transformation->getSource().c_str());
433 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
434 : );
435 1 : return false;
436 : }
437 :
438 1 : LOGDEBUG(
439 : std::string msg = ert::tracing::Logger::asString("Extracted object from client event: %s", sourceVault.asString().c_str());
440 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
441 : );
442 1 : break;
443 24 : }
444 : // Not applicable in server context:
445 0 : case Transformation::SourceType::Sendseq:
446 : case Transformation::SourceType::Seq:
447 : case Transformation::SourceType::ResponseHeader:
448 : case Transformation::SourceType::ResponseStatusCode:
449 0 : return false;
450 : }
451 :
452 :
453 121 : return true;
454 : }
455 :
456 25 : bool AdminServerProvision::processFilters(std::shared_ptr<Transformation> transformation,
457 : TypeConverter& sourceVault,
458 : const std::map<std::string, std::string>& variables,
459 : std::smatch &matches,
460 : std::string &source) const
461 : {
462 25 : bool success = false;
463 25 : std::string targetS;
464 25 : std::int64_t targetI = 0;
465 25 : std::uint64_t targetU = 0;
466 25 : double targetF = 0;
467 :
468 : // all the filters except Sum/Multiply, require a string target
469 25 : if (transformation->getFilterType() != Transformation::FilterType::Sum && transformation->getFilterType() != Transformation::FilterType::Multiply) {
470 19 : source = sourceVault.getString(success);
471 19 : if (!success) return false;
472 : }
473 :
474 : // All our regex are built with 'std::regex::optimize' so they are already validated and regex functions cannot throw exception:
475 : //try { // std::regex exceptions
476 25 : switch (transformation->getFilterType()) {
477 3 : case Transformation::FilterType::RegexCapture:
478 : {
479 3 : if (std::regex_match(source, matches, transformation->getFilterRegex()) && matches.size() >=1) {
480 2 : targetS = matches.str(0);
481 2 : sourceVault.setString(targetS);
482 2 : LOGDEBUG(
483 : std::stringstream ss;
484 : ss << "Regex matches: Size = " << matches.size();
485 : for(size_t i=0; i < matches.size(); i++) {
486 : ss << " | [" << i << "] = " << matches.str(i);
487 : }
488 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
489 : );
490 : }
491 : else {
492 1 : LOGDEBUG(
493 : std::string msg = ert::tracing::Logger::asString("Unable to match '%s' againt regex capture '%s' in transformation item", source.c_str(), transformation->getFilter().c_str());
494 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
495 : );
496 1 : return false;
497 : }
498 2 : break;
499 : }
500 1 : case Transformation::FilterType::RegexReplace:
501 : {
502 1 : targetS = std::regex_replace (source, transformation->getFilterRegex(), transformation->getFilter() /* fmt */);
503 1 : sourceVault.setString(targetS);
504 1 : break;
505 : }
506 1 : case Transformation::FilterType::Append:
507 : {
508 1 : std::string filter = transformation->getFilter();
509 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
510 :
511 1 : targetS = source + filter;
512 1 : sourceVault.setString(targetS);
513 1 : break;
514 1 : }
515 1 : case Transformation::FilterType::Prepend:
516 : {
517 1 : std::string filter = transformation->getFilter();
518 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
519 :
520 1 : targetS = filter + source;
521 1 : sourceVault.setString(targetS);
522 1 : break;
523 1 : }
524 3 : case Transformation::FilterType::Sum:
525 : {
526 3 : switch (transformation->getFilterNumberType()) {
527 1 : case 0: /* integer */
528 : {
529 1 : targetI = sourceVault.getInteger(success);
530 1 : if (success) targetI += transformation->getFilterI();
531 : //else return false; // should not happen (protected by schema)
532 1 : sourceVault.setInteger(targetI);
533 1 : break;
534 : }
535 1 : case 1: /* unsigned */
536 : {
537 1 : targetU = sourceVault.getUnsigned(success);
538 1 : if (success) targetU += transformation->getFilterU();
539 : //else return false; // should not happen (protected by schema)
540 1 : sourceVault.setUnsigned(targetU);
541 1 : break;
542 : }
543 1 : case 2: /* double */
544 : {
545 1 : targetF = sourceVault.getFloat(success);
546 1 : if (success) targetF += transformation->getFilterF();
547 : //else return false; // should not happen (protected by schema)
548 1 : sourceVault.setFloat(targetF);
549 1 : break;
550 : }
551 : }
552 3 : break;
553 : }
554 3 : case Transformation::FilterType::Multiply:
555 : {
556 3 : switch (transformation->getFilterNumberType()) {
557 1 : case 0: /* integer */
558 : {
559 1 : targetI = sourceVault.getInteger(success);
560 1 : if (success) targetI *= transformation->getFilterI();
561 : //else return false; // should not happen (protected by schema)
562 1 : sourceVault.setInteger(targetI);
563 1 : break;
564 : }
565 1 : case 1: /* unsigned */
566 : {
567 1 : targetU = sourceVault.getUnsigned(success);
568 1 : if (success) targetU *= transformation->getFilterU();
569 : //else return false; // should not happen (protected by schema)
570 1 : sourceVault.setUnsigned(targetU);
571 1 : break;
572 : }
573 1 : case 2: /* double */
574 : {
575 1 : targetF = sourceVault.getFloat(success);
576 1 : if (success) targetF *= transformation->getFilterF();
577 : //else return false; // should not happen (protected by schema)
578 1 : sourceVault.setFloat(targetF);
579 1 : break;
580 : }
581 : }
582 3 : break;
583 : }
584 4 : case Transformation::FilterType::ConditionVar: // TODO: if condition is false, source storage could be omitted to improve performance
585 : {
586 : // Get variable value for the variable name 'transformation->getFilter()':
587 4 : std::string varname = transformation->getFilter();
588 4 : bool reverse = (transformation->getFilter()[0] == '!');
589 4 : if (reverse) {
590 2 : varname.erase(0,1);
591 : }
592 4 : auto iter = variables.find(varname);
593 4 : bool varFound = (iter != variables.end());
594 4 : std::string varvalue{};
595 4 : if (varFound) {
596 1 : varvalue = iter->second;
597 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (local)", varname.c_str()), ERT_FILE_LOCATION));
598 : }
599 : else {
600 3 : varFound = global_variable_->tryGet(varname, varvalue);
601 3 : LOGDEBUG(if (varFound) ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (global)", varname.c_str()), ERT_FILE_LOCATION));
602 : }
603 :
604 4 : bool conditionVar = (varFound && !(varvalue.empty()));
605 4 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable value: '%s'", (varFound ? varvalue.c_str():"<undefined>")), ERT_FILE_LOCATION));
606 :
607 4 : if ((reverse && !conditionVar)||(!reverse && conditionVar)) {
608 3 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is true", (reverse ? "!":"")), ERT_FILE_LOCATION));
609 3 : sourceVault.setString(source);
610 : }
611 : else {
612 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is false", (reverse ? "!":"")), ERT_FILE_LOCATION));
613 1 : return false;
614 : }
615 3 : break;
616 8 : }
617 2 : case Transformation::FilterType::EqualTo:
618 : {
619 2 : std::string filter = transformation->getFilter();
620 2 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
621 :
622 : // Get value for the comparison 'transformation->getFilter()':
623 2 : if (source == filter) {
624 1 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is true", ERT_FILE_LOCATION));
625 1 : sourceVault.setString(source);
626 : }
627 : else {
628 1 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is false", ERT_FILE_LOCATION));
629 1 : return false;
630 : }
631 1 : break;
632 2 : }
633 2 : case Transformation::FilterType::DifferentFrom:
634 : {
635 2 : std::string filter = transformation->getFilter();
636 2 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
637 :
638 : // Get value for the comparison 'transformation->getFilter()':
639 2 : if (source != filter) {
640 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is true", ERT_FILE_LOCATION));
641 1 : sourceVault.setString(source);
642 : }
643 : else {
644 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is false", ERT_FILE_LOCATION));
645 1 : return false;
646 : }
647 1 : break;
648 2 : }
649 3 : case Transformation::FilterType::JsonConstraint:
650 : {
651 3 : nlohmann::json sobj = sourceVault.getObject(success);
652 : // should not happen (protected by schema)
653 : //if (!success) {
654 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for JsonConstraint filter must be a valid json object", ERT_FILE_LOCATION));
655 : // return false;
656 : //}
657 3 : std::string failReport;
658 3 : if (h2agent::model::jsonConstraint(sobj, transformation->getFilterObject(), failReport)) {
659 2 : sourceVault.setString("1");
660 : }
661 : else {
662 2 : sourceVault.setString(failReport);
663 : }
664 3 : break;
665 3 : }
666 2 : case Transformation::FilterType::SchemaId:
667 : {
668 2 : nlohmann::json sobj = sourceVault.getObject(success);
669 : // should not happen (protected by schema)
670 : //if (!success) {
671 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for SchemaId filter must be a valid json object", ERT_FILE_LOCATION));
672 : // return false;
673 : //}
674 2 : std::string failReport;
675 2 : auto schema = admin_data_->getSchemaData().find(transformation->getFilter()); // TODO: find a way to cache this (set the schema into transformation: but clean schemas should be detected to avoid corruption)
676 2 : if (schema) {
677 2 : if (schema->validate(sobj, failReport)) {
678 2 : sourceVault.setString("1");
679 : }
680 : else {
681 1 : sourceVault.setString(failReport);
682 : }
683 : }
684 : else {
685 0 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in transformation item: VALIDATION will be IGNORED", transformation->getFilter().c_str()), ERT_FILE_LOCATION);
686 : }
687 2 : break;
688 2 : }
689 : }
690 : //}
691 : //catch (std::exception& e)
692 : //{
693 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
694 : //}
695 :
696 :
697 21 : return true;
698 25 : }
699 :
700 117 : bool AdminServerProvision::processTargets(std::shared_ptr<Transformation> transformation,
701 : TypeConverter &sourceVault,
702 : std::map<std::string, std::string>& variables,
703 : const std::smatch &matches,
704 : bool eraser,
705 : bool hasFilter,
706 : unsigned int &responseStatusCode,
707 : nlohmann::json &responseBodyJson,
708 : std::string &responseBodyAsString,
709 : nghttp2::asio_http2::header_map &responseHeaders,
710 : unsigned int &responseDelayMs,
711 : std::string &outState,
712 : std::string &outStateMethod,
713 : std::string &outStateUri,
714 : std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
715 : bool &breakCondition) const
716 : {
717 117 : bool success = false;
718 117 : std::string targetS;
719 117 : std::int64_t targetI = 0;
720 117 : std::uint64_t targetU = 0;
721 117 : double targetF = 0;
722 117 : bool boolean = false;
723 117 : nlohmann::json obj;
724 :
725 :
726 : try { // nlohmann::json exceptions
727 :
728 117 : std::string target = transformation->getTarget();
729 117 : std::string target2 = transformation->getTarget2(); // foreign outState URI
730 :
731 117 : replaceVariables(target, transformation->getTargetPatterns(), variables, global_variable_);
732 117 : if (!target2.empty()) {
733 1 : replaceVariables(target2, transformation->getTarget2Patterns(), variables, global_variable_);
734 : }
735 :
736 117 : switch (transformation->getTargetType()) {
737 44 : case Transformation::TargetType::ResponseBodyString:
738 : {
739 : // extraction
740 44 : targetS = sourceVault.getString(success);
741 44 : if (!success) return false;
742 : // assignment
743 44 : responseBodyAsString = targetS;
744 44 : break;
745 : }
746 1 : case Transformation::TargetType::ResponseBodyHexString:
747 : {
748 : // extraction
749 1 : targetS = sourceVault.getString(success);
750 1 : if (!success) return false;
751 : // assignment
752 1 : if (!h2agent::model::fromHexString(targetS, responseBodyAsString)) return false;
753 1 : break;
754 : }
755 12 : case Transformation::TargetType::ResponseBodyJson_String:
756 : {
757 : // extraction
758 12 : targetS = sourceVault.getString(success);
759 12 : if (!success) return false;
760 : // assignment
761 12 : nlohmann::json::json_pointer j_ptr(target);
762 11 : responseBodyJson[j_ptr] = targetS;
763 11 : break;
764 11 : }
765 2 : case Transformation::TargetType::ResponseBodyJson_Integer:
766 : {
767 : // extraction
768 2 : targetI = sourceVault.getInteger(success);
769 2 : if (!success) return false;
770 : // assignment
771 2 : nlohmann::json::json_pointer j_ptr(target);
772 2 : responseBodyJson[j_ptr] = targetI;
773 2 : break;
774 2 : }
775 2 : case Transformation::TargetType::ResponseBodyJson_Unsigned:
776 : {
777 : // extraction
778 2 : targetU = sourceVault.getUnsigned(success);
779 2 : if (!success) return false;
780 : // assignment
781 2 : nlohmann::json::json_pointer j_ptr(target);
782 2 : responseBodyJson[j_ptr] = targetU;
783 2 : break;
784 2 : }
785 2 : case Transformation::TargetType::ResponseBodyJson_Float:
786 : {
787 : // extraction
788 2 : targetF = sourceVault.getFloat(success);
789 2 : if (!success) return false;
790 : // assignment
791 2 : nlohmann::json::json_pointer j_ptr(target);
792 2 : responseBodyJson[j_ptr] = targetF;
793 2 : break;
794 2 : }
795 2 : case Transformation::TargetType::ResponseBodyJson_Boolean:
796 : {
797 : // extraction
798 2 : boolean = sourceVault.getBoolean(success);
799 2 : if (!success) return false;
800 : // assignment
801 2 : nlohmann::json::json_pointer j_ptr(target);
802 2 : responseBodyJson[j_ptr] = boolean;
803 2 : break;
804 2 : }
805 14 : case Transformation::TargetType::ResponseBodyJson_Object:
806 : {
807 :
808 14 : if (eraser) {
809 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into json path '%s'", target.c_str()), ERT_FILE_LOCATION));
810 2 : if (target.empty()) {
811 1 : responseBodyJson.erase(responseBodyJson.begin(), responseBodyJson.end());
812 1 : return false;
813 : }
814 :
815 : //erase() DOES NOT SUPPORT JSON POINTERS:
816 : //nlohmann::json::json_pointer j_ptr(target);
817 : //responseBodyJson.erase(j_ptr);
818 : //
819 : // For a path '/a/b/c' we must access to /a/b and then erase "c":
820 1 : size_t lastSlashPos = target.find_last_of("/");
821 : // lastSlashPos will never be std::string::npos here
822 1 : std::string parentPath = target.substr(0, lastSlashPos);
823 1 : std::string childKey = "";
824 1 : if (lastSlashPos + 1 < target.size()) childKey = target.substr(lastSlashPos + 1, target.size());
825 1 : nlohmann::json::json_pointer j_ptr(parentPath);
826 1 : responseBodyJson[j_ptr].erase(childKey);
827 1 : return false;
828 1 : }
829 :
830 : // extraction will be object if possible, falling back to the rest of formats with this priority: string, integer, unsigned, float, boolean
831 : // assignment for valid extraction
832 12 : nlohmann::json::json_pointer j_ptr(target);
833 :
834 : // Native types for SOURCES:
835 : //
836 : // [string] request.uri, request.uri.path, request.header, randomset, strftime, var, globalVar, value, txtFile, binFile, command
837 : // [object] request.body, response.body, serverEvent (when target is also object, it could be promoted to string, unsigned, integer, float or boolean).
838 : // [integer] random, timestamp
839 : // [unsigned] recvseq
840 : // [float] math.*
841 : // [boolean] NONE
842 : // So, depending on the target, the corresponding getter (getInteger, getString, etc.) will be used: WE DO NOT WANT FORCE CONVERSIONS:
843 : // Note that there is not sources with boolean as native type, so boolean getter is never reached (so commented to avoid UT coverage fault).
844 : //
845 12 : switch (sourceVault.getNativeType()) {
846 2 : case TypeConverter::NativeType::Object:
847 2 : obj = sourceVault.getObject(success);
848 2 : if (success) {
849 2 : if (target.empty()) {
850 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
851 : }
852 : else {
853 1 : responseBodyJson[j_ptr] = obj;
854 : }
855 : }
856 2 : break;
857 :
858 2 : case TypeConverter::NativeType::String:
859 2 : targetS = sourceVault.getString(success);
860 2 : if (success) responseBodyJson[j_ptr] = targetS;
861 2 : break;
862 :
863 5 : case TypeConverter::NativeType::Integer:
864 5 : targetI = sourceVault.getInteger(success);
865 5 : if (success) responseBodyJson[j_ptr] = targetI;
866 5 : break;
867 :
868 1 : case TypeConverter::NativeType::Unsigned:
869 1 : targetU = sourceVault.getUnsigned(success);
870 1 : if (success) responseBodyJson[j_ptr] = targetU;
871 1 : break;
872 :
873 1 : case TypeConverter::NativeType::Float:
874 1 : targetF = sourceVault.getFloat(success);
875 1 : if (success) responseBodyJson[j_ptr] = targetF;
876 1 : break;
877 :
878 : // Not reached at the moment:
879 1 : case TypeConverter::NativeType::Boolean:
880 1 : boolean = sourceVault.getBoolean(success);
881 1 : if (success) responseBodyJson[j_ptr] = boolean;
882 1 : break;
883 : }
884 12 : break;
885 12 : }
886 3 : case Transformation::TargetType::ResponseBodyJson_JsonString:
887 : {
888 :
889 : // assignment for valid extraction
890 3 : nlohmann::json::json_pointer j_ptr(target);
891 :
892 : // extraction
893 3 : targetS = sourceVault.getString(success);
894 3 : if (!success) return false;
895 3 : if (!h2agent::model::parseJsonContent(targetS, obj))
896 1 : return false;
897 :
898 : // assignment
899 2 : if (target.empty()) {
900 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
901 : }
902 : else {
903 1 : responseBodyJson[j_ptr] = obj;
904 : }
905 2 : break;
906 3 : }
907 1 : case Transformation::TargetType::ResponseHeader_t:
908 : {
909 : // extraction
910 1 : targetS = sourceVault.getString(success);
911 1 : if (!success) return false;
912 : // assignment
913 1 : responseHeaders.emplace(target, nghttp2::asio_http2::header_value{targetS});
914 1 : break;
915 : }
916 5 : case Transformation::TargetType::ResponseStatusCode_t:
917 : {
918 : // extraction
919 5 : targetU = sourceVault.getUnsigned(success);
920 5 : if (!success) return false;
921 : // assignment
922 5 : responseStatusCode = targetU;
923 5 : break;
924 : }
925 1 : case Transformation::TargetType::ResponseDelayMs:
926 : {
927 : // extraction
928 1 : targetU = sourceVault.getUnsigned(success);
929 1 : if (!success) return false;
930 : // assignment
931 1 : responseDelayMs = targetU;
932 1 : break;
933 : }
934 9 : case Transformation::TargetType::TVar:
935 : {
936 9 : if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
937 1 : std::string varname;
938 1 : if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
939 1 : variables[target] = matches.str(0); // variable "as is" stores the entire match
940 4 : for(size_t i=1; i < matches.size(); i++) {
941 3 : varname = target;
942 3 : varname += ".";
943 3 : varname += std::to_string(i);
944 3 : variables[varname] = matches.str(i);
945 3 : LOGDEBUG(
946 : std::stringstream ss;
947 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
948 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
949 : );
950 : }
951 : }
952 1 : }
953 : else {
954 : // extraction
955 8 : targetS = sourceVault.getString(success);
956 8 : if (!success) return false;
957 :
958 8 : if (hasFilter) {
959 4 : if(transformation->getFilterType() == Transformation::FilterType::JsonConstraint) {
960 1 : if (targetS != "1") { // this is a fail report
961 1 : variables[target + ".fail"] = targetS;
962 1 : targetS = "";
963 : }
964 : }
965 3 : else if (transformation->getFilterType() == Transformation::FilterType::SchemaId) {
966 0 : if (targetS != "1") { // this is a fail report
967 0 : variables[target + ".fail"] = targetS;
968 0 : targetS = "";
969 : }
970 : }
971 : }
972 :
973 : // assignment
974 8 : variables[target] = targetS;
975 : }
976 9 : break;
977 : }
978 4 : case Transformation::TargetType::TGVar:
979 : {
980 4 : if (eraser) {
981 : bool exists;
982 1 : global_variable_->remove(target, exists);
983 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into global variable '%s' (%s)", target.c_str(), exists ? "removed":"missing"), ERT_FILE_LOCATION));
984 : }
985 3 : else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
986 1 : std::string varname;
987 1 : if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
988 1 : global_variable_->load(target, matches.str(0)); // variable "as is" stores the entire match
989 4 : for(size_t i=1; i < matches.size(); i++) {
990 3 : varname = target;
991 3 : varname += ".";
992 3 : varname += std::to_string(i);
993 3 : global_variable_->load(varname, matches.str(i));
994 3 : LOGDEBUG(
995 : std::stringstream ss;
996 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
997 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
998 : );
999 : }
1000 : }
1001 1 : }
1002 : else {
1003 : // extraction
1004 2 : targetS = sourceVault.getString(success);
1005 2 : if (!success) return false;
1006 : // assignment
1007 2 : global_variable_->load(target, targetS);
1008 : }
1009 4 : break;
1010 : }
1011 2 : case Transformation::TargetType::OutState:
1012 : {
1013 : // extraction
1014 2 : targetS = sourceVault.getString(success);
1015 2 : if (!success) return false;
1016 : // assignments
1017 2 : outState = targetS;
1018 2 : outStateMethod = target; // empty on regular usage
1019 2 : outStateUri = target2; // empty on regular usage
1020 2 : break;
1021 : }
1022 2 : case Transformation::TargetType::TTxtFile:
1023 : {
1024 : // extraction
1025 2 : targetS = sourceVault.getString(success);
1026 2 : if (!success) return false;
1027 :
1028 2 : if (eraser) {
1029 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into text file '%s'", target.c_str()), ERT_FILE_LOCATION));
1030 1 : file_manager_->empty(target/*path*/);
1031 : }
1032 : else {
1033 : // assignments
1034 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
1035 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
1036 : // files type.
1037 1 : file_manager_->write(target/*path*/, targetS/*data*/, true/*text*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
1038 : }
1039 2 : break;
1040 : }
1041 2 : case Transformation::TargetType::TBinFile:
1042 : {
1043 : // extraction
1044 2 : targetS = sourceVault.getString(success);
1045 2 : if (!success) return false;
1046 :
1047 2 : if (eraser) {
1048 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into binary file '%s'", target.c_str()), ERT_FILE_LOCATION));
1049 1 : file_manager_->empty(target/*path*/);
1050 : }
1051 : else {
1052 : // assignments
1053 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
1054 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
1055 : // files type.
1056 1 : file_manager_->write(target/*path*/, targetS/*data*/, false/*binary*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
1057 : }
1058 2 : break;
1059 : }
1060 1 : case Transformation::TargetType::UDPSocket:
1061 : {
1062 : // extraction
1063 1 : targetS = sourceVault.getString(success);
1064 1 : if (!success) return false;
1065 :
1066 : // assignments
1067 : // Possible delay provided in 'target': <path>|<delay>
1068 1 : std::string path = target;
1069 1 : size_t lastDotPos = target.find_last_of("|");
1070 1 : unsigned int delayMs = atoi(target.substr(lastDotPos + 1).c_str());
1071 1 : path = target.substr(0, lastDotPos);
1072 :
1073 1 : LOGDEBUG(
1074 : std::string msg = ert::tracing::Logger::asString("UDPSocket '%s' target, delayed %u milliseconds, in transformation item", path.c_str(), delayMs);
1075 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1076 : );
1077 :
1078 1 : socket_manager_->write(path, targetS/*data*/, delayMs * 1000 /* usecs */);
1079 1 : break;
1080 1 : }
1081 3 : case Transformation::TargetType::ServerEventToPurge:
1082 : {
1083 3 : if (!eraser) {
1084 1 : LOGDEBUG(ert::tracing::Logger::debug("'ServerEventToPurge' target type only works with 'eraser' source type. This transformation will be ignored.", ERT_FILE_LOCATION));
1085 2 : return false;
1086 : }
1087 : // transformation->getTargetTokenized() is a vector:
1088 : //
1089 : // requestMethod: index 0
1090 : // requestUri: index 1
1091 : // eventNumber: index 2
1092 2 : std::string event_method = transformation->getTargetTokenized()[0];
1093 2 : replaceVariables(event_method, transformation->getTargetPatterns(), variables, global_variable_);
1094 2 : std::string event_uri = transformation->getTargetTokenized()[1];
1095 2 : replaceVariables(event_uri, transformation->getTargetPatterns(), variables, global_variable_);
1096 2 : std::string event_number = transformation->getTargetTokenized()[2];
1097 2 : replaceVariables(event_number, transformation->getTargetPatterns(), variables, global_variable_);
1098 :
1099 2 : bool serverDataDeleted = false;
1100 2 : EventKey ekey(event_method, event_uri, event_number);
1101 2 : bool success = mock_server_events_data_->clear(serverDataDeleted, ekey);
1102 :
1103 2 : if (!success) {
1104 1 : LOGDEBUG(
1105 : std::string msg = ert::tracing::Logger::asString("Unexpected error while removing server data event '%s' in transformation item", transformation->getTarget().c_str());
1106 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1107 : );
1108 1 : return false;
1109 : }
1110 :
1111 1 : LOGDEBUG(
1112 : std::string msg = ert::tracing::Logger::asString("Server event '%s' removal result: %s", transformation->getTarget().c_str(), (serverDataDeleted ? "SUCCESS":"NOTHING REMOVED"));
1113 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1114 : );
1115 1 : break;
1116 8 : }
1117 3 : case Transformation::TargetType::ClientProvision_t:
1118 : {
1119 3 : std::string inState = DEFAULT_ADMIN_PROVISION_STATE; // "initial" by default
1120 3 : if (!eraser) {
1121 2 : targetS = sourceVault.getString(success);
1122 2 : if (success && !targetS.empty()) inState = targetS;
1123 : }
1124 3 : std::string clientProvisionId = transformation->getTarget();
1125 3 : replaceVariables(clientProvisionId, transformation->getTargetPatterns(), variables, global_variable_);
1126 3 : replaceVariables(inState, transformation->getSourcePatterns(), variables, global_variable_);
1127 3 : clientProvisionTriggers.emplace_back(clientProvisionId, inState);
1128 3 : LOGDEBUG(
1129 : std::string msg = ert::tracing::Logger::asString("Scheduled client provision trigger: id='%s', inState='%s'", clientProvisionId.c_str(), inState.c_str());
1130 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1131 : );
1132 3 : break;
1133 3 : }
1134 2 : case Transformation::TargetType::Break:
1135 : {
1136 : // extraction
1137 2 : targetS = sourceVault.getString(success);
1138 2 : if (!success) return false;
1139 : // assignments
1140 2 : if (targetS.empty()) {
1141 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action ignored (empty string as source provided)", ERT_FILE_LOCATION));
1142 1 : return false;
1143 : }
1144 :
1145 1 : breakCondition = true;
1146 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action triggered: ignoring remaining transformation items", ERT_FILE_LOCATION));
1147 1 : return false;
1148 : break;
1149 : }
1150 : // this won't happen due to schema for server target types:
1151 0 : case Transformation::TargetType::RequestBodyString:
1152 : case Transformation::TargetType::RequestBodyHexString:
1153 : case Transformation::TargetType::RequestBodyJson_String:
1154 : case Transformation::TargetType::RequestBodyJson_Integer:
1155 : case Transformation::TargetType::RequestBodyJson_Unsigned:
1156 : case Transformation::TargetType::RequestBodyJson_Float:
1157 : case Transformation::TargetType::RequestBodyJson_Boolean:
1158 : case Transformation::TargetType::RequestBodyJson_Object:
1159 : case Transformation::TargetType::RequestBodyJson_JsonString:
1160 : case Transformation::TargetType::RequestHeader_t:
1161 : case Transformation::TargetType::RequestDelayMs:
1162 : case Transformation::TargetType::RequestTimeoutMs:
1163 : case Transformation::TargetType::ClientEventToPurge:
1164 : case Transformation::TargetType::RequestUri_t:
1165 : case Transformation::TargetType::RequestMethod_t:
1166 0 : break;
1167 : }
1168 125 : }
1169 1 : catch (std::exception& e)
1170 : {
1171 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1172 1 : }
1173 :
1174 :
1175 110 : return true;
1176 117 : }
1177 :
1178 105 : void AdminServerProvision::transform( const std::string &requestUri,
1179 : const std::string &requestUriPath,
1180 : const std::map<std::string, std::string> &requestQueryParametersMap,
1181 : DataPart &requestBodyDataPart,
1182 : const nghttp2::asio_http2::header_map &requestHeaders,
1183 : std::uint64_t generalUniqueServerSequence,
1184 :
1185 : /* OUTPUT PARAMETERS WHICH ALREADY HAVE DEFAULT VALUES BEFORE TRANSFORMATIONS: */
1186 : unsigned int &responseStatusCode,
1187 : nghttp2::asio_http2::header_map &responseHeaders,
1188 : std::string &responseBody,
1189 : unsigned int &responseDelayMs,
1190 : std::string &outState,
1191 : std::string &outStateMethod,
1192 : std::string &outStateUri,
1193 : std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
1194 : std::map<std::string, std::string> &variables
1195 : )
1196 : {
1197 : // Default values without transformations:
1198 105 : responseStatusCode = getResponseCode();
1199 105 : responseHeaders = getResponseHeaders();
1200 105 : responseDelayMs = getResponseDelayMilliseconds();
1201 105 : outState = getOutState(); // prepare next request state, with URI path before transformed with matching algorithms
1202 105 : outStateMethod = "";
1203 105 : outStateUri = "";
1204 :
1205 : // Check if the request body must be decoded:
1206 105 : bool mustDecodeRequestBody = false;
1207 105 : if (getRequestSchema()) {
1208 2 : mustDecodeRequestBody = true;
1209 : }
1210 : else {
1211 222 : for (const auto &t : transformations_) {
1212 135 : if (t->getSourceType() == Transformation::SourceType::RequestBody) {
1213 16 : if (!requestBodyDataPart.str().empty()) {
1214 15 : mustDecodeRequestBody = true;
1215 : }
1216 : else {
1217 1 : LOGINFORMATIONAL(ert::tracing::Logger::informational("Empty request body received: some transformations will be ignored", ERT_FILE_LOCATION));
1218 : }
1219 16 : break;
1220 : }
1221 : }
1222 : }
1223 105 : if (mustDecodeRequestBody) {
1224 17 : requestBodyDataPart.decode(requestHeaders);
1225 : }
1226 :
1227 : // Request schema validation (normally used to validate native json received, but can also be used to validate the agent json representation (multipart, text, etc.)):
1228 105 : if (getRequestSchema()) {
1229 2 : std::string error{};
1230 2 : if (!getRequestSchema()->validate(requestBodyDataPart.getJson(), error)) {
1231 1 : responseStatusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
1232 1 : return; // INTERRUPT TRANSFORMATIONS
1233 : }
1234 2 : }
1235 :
1236 : // Find out if response body will need to be cloned (this is true if any transformation uses it as target):
1237 104 : bool usesResponseBodyAsTransformationJsonTarget = false;
1238 198 : for (const auto &t : transformations_) {
1239 239 : if (t->getTargetType() == Transformation::TargetType::ResponseBodyJson_String ||
1240 230 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Integer ||
1241 226 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Unsigned ||
1242 222 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Float ||
1243 218 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Boolean ||
1244 347 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Object ||
1245 97 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_JsonString) {
1246 29 : usesResponseBodyAsTransformationJsonTarget = true;
1247 29 : break;
1248 : }
1249 : }
1250 :
1251 104 : nlohmann::json responseBodyJson;
1252 104 : if (usesResponseBodyAsTransformationJsonTarget) {
1253 29 : responseBodyJson = getResponseBody(); // clone provision response body to manipulate this copy and finally we will dump() it over 'responseBody':
1254 : // if(usesResponseBodyAsTransformationJsonTarget) responseBody = responseBodyJson.dump(); <--- place this after transformations (*)
1255 : }
1256 : else {
1257 75 : responseBody = getResponseBodyAsString(); // this could be overwritten by targets ResponseBodyString or ResponseBodyHexString
1258 : }
1259 :
1260 : // Type converter:
1261 104 : TypeConverter sourceVault{};
1262 :
1263 : // Apply transformations sequentially
1264 104 : bool breakCondition = false;
1265 240 : for (const auto &transformation : transformations_) {
1266 :
1267 137 : if (breakCondition) break;
1268 :
1269 136 : bool eraser = false;
1270 :
1271 136 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Processing transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1272 :
1273 : // SOURCES: RequestUri, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser, Math, Random, Timestamp, Strftime, Recvseq, SVar, SGvar, Value, ServerEvent, InState
1274 136 : if (!processSources(transformation, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, eraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) {
1275 15 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on source", ERT_FILE_LOCATION));
1276 26 : continue;
1277 : }
1278 :
1279 121 : std::smatch matches; // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
1280 : // So, we can't use 'matches' as container because source may change: BUT, using that source exclusively, it will work (*)
1281 121 : std::string source; // Now, this never will be out of scope, and 'matches' will be valid.
1282 :
1283 : // FILTERS: RegexCapture, RegexReplace, Append, Prepend, Sum, Multiply, ConditionVar, EqualTo, DifferentFrom, JsonConstraint, SchemaId
1284 121 : bool hasFilter = transformation->hasFilter();
1285 121 : if (hasFilter) {
1286 25 : if (eraser || !processFilters(transformation, sourceVault, variables, matches, source)) {
1287 4 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on filter", ERT_FILE_LOCATION));
1288 4 : if (eraser) LOGWARNING(ert::tracing::Logger::warning("Filter is not allowed when using 'eraser' source type. Transformation will be ignored.", ERT_FILE_LOCATION));
1289 4 : continue;
1290 : }
1291 : }
1292 :
1293 : // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break
1294 117 : if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition)) {
1295 7 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
1296 7 : continue;
1297 : }
1298 :
1299 132 : }
1300 :
1301 : // (*) Regenerate final responseBody after transformations:
1302 104 : if(usesResponseBodyAsTransformationJsonTarget && !responseBodyJson.empty()) {
1303 : try {
1304 28 : responseBody = responseBodyJson.dump(); // this may arise type error, for example in case of trying to set json field value with binary data:
1305 : // When having a provision transformation from 'request.body' to 'response.body.json.string./whatever':
1306 : // echo -en '\x80\x01' | curl --http2-prior-knowledge -i -H 'content-type:application/octet-stream' -X GET "<traffic url>/uri" --data-binary @-
1307 : //
1308 : // This is not valid and must be protected. The user should use another kind of target to store binary.
1309 : }
1310 1 : catch (const std::exception& e)
1311 : {
1312 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1313 1 : }
1314 : }
1315 :
1316 : // Response schema validation (not supported for response body created by non-json targets, to simplify the fact to parse need on ResponseBodyString/ResponseBodyHexString):
1317 104 : if (getResponseSchema()) {
1318 1 : std::string error{};
1319 1 : if (!getResponseSchema()->validate(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), error)) {
1320 1 : responseStatusCode = ert::http2comm::ResponseCode::INTERNAL_SERVER_ERROR; // 500: built response will be anyway sent although status code is overwritten with internal server error.
1321 : }
1322 1 : }
1323 104 : }
1324 :
1325 123 : bool AdminServerProvision::load(const nlohmann::json &j, bool regexMatchingConfigured) {
1326 :
1327 : // Store whole document (useful for GET operation)
1328 123 : json_ = j;
1329 :
1330 : // Mandatory
1331 123 : auto requestMethod_it = j.find("requestMethod");
1332 123 : request_method_ = *requestMethod_it;
1333 :
1334 123 : auto it = j.find("responseCode");
1335 123 : response_code_ = *it;
1336 :
1337 : // Optional
1338 123 : it = j.find("requestUri");
1339 123 : if (it != j.end() && it->is_string()) {
1340 123 : request_uri_ = *it;
1341 : }
1342 :
1343 123 : it = j.find("inState");
1344 123 : if (it != j.end() && it->is_string()) {
1345 3 : in_state_ = *it;
1346 3 : if (in_state_.empty()) in_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1347 : }
1348 :
1349 123 : it = j.find("outState");
1350 123 : if (it != j.end() && it->is_string()) {
1351 2 : out_state_ = *it;
1352 2 : if (out_state_.empty()) out_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1353 : }
1354 :
1355 123 : it = j.find("requestSchemaId");
1356 123 : if (it != j.end() && it->is_string()) {
1357 7 : request_schema_id_ = *it;
1358 7 : if (request_schema_id_.empty()) {
1359 1 : ert::tracing::Logger::error("Invalid empty request schema identifier", ERT_FILE_LOCATION);
1360 1 : return false;
1361 : }
1362 : }
1363 :
1364 122 : it = j.find("responseSchemaId");
1365 122 : if (it != j.end() && it->is_string()) {
1366 7 : response_schema_id_ = *it;
1367 7 : if (response_schema_id_.empty()) {
1368 1 : ert::tracing::Logger::error("Invalid empty response schema identifier", ERT_FILE_LOCATION);
1369 1 : return false;
1370 : }
1371 : }
1372 :
1373 121 : it = j.find("responseHeaders");
1374 121 : if (it != j.end() && it->is_object()) {
1375 339 : for (auto& [key, val] : it->items())
1376 339 : response_headers_.emplace(key, nghttp2::asio_http2::header_value{val});
1377 : }
1378 :
1379 121 : it = j.find("responseBody");
1380 121 : if (it != j.end()) {
1381 113 : if (it->is_object() || it->is_array()) {
1382 107 : response_body_ = *it;
1383 107 : response_body_string_ = response_body_.dump(); // valid as cache for static responses (not updated with transformations)
1384 : }
1385 6 : else if (it->is_string()) {
1386 1 : response_body_string_ = *it;
1387 : }
1388 5 : else if (it->is_number_integer() || it->is_number_unsigned()) {
1389 : //response_body_integer_ = *it;
1390 2 : int number = *it;
1391 2 : response_body_string_ = std::to_string(number);
1392 : }
1393 3 : else if (it->is_number_float()) {
1394 : //response_body_number_ = *it;
1395 1 : response_body_string_ = std::to_string(double(*it));
1396 : }
1397 2 : else if (it->is_boolean()) {
1398 : //response_body_boolean_ = *it;
1399 1 : response_body_string_ = ((bool)(*it) ? "true":"false");
1400 : }
1401 1 : else if (it->is_null()) {
1402 : //response_body_null_ = true;
1403 1 : response_body_string_ = "null";
1404 : }
1405 : }
1406 :
1407 121 : it = j.find("responseDelayMs");
1408 121 : if (it != j.end() && it->is_number()) {
1409 108 : response_delay_ms_ = *it;
1410 : }
1411 :
1412 121 : auto transform_it = j.find("transform");
1413 121 : if (transform_it != j.end()) {
1414 247 : for (auto it : *transform_it) { // "it" is of type json::reference and has no key() member
1415 145 : loadTransformation(it);
1416 145 : }
1417 : }
1418 :
1419 : // Store key:
1420 121 : h2agent::model::calculateStringKey(key_, in_state_, request_method_, request_uri_);
1421 :
1422 121 : if (regexMatchingConfigured) {
1423 : // Precompile regex with key, only for 'RegexMatching' algorithm:
1424 : try {
1425 6 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Assigning regex: %s", key_.c_str()), ERT_FILE_LOCATION));
1426 6 : regex_.assign(key_, std::regex::optimize);
1427 : }
1428 2 : catch (std::regex_error &e) {
1429 4 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1430 2 : ert::tracing::Logger::error("Invalid regular expression (detected when joining 'inState' and/or 'requestUri' from provision) for current 'RegexMatching' server matching algorithm", ERT_FILE_LOCATION);
1431 2 : return false;
1432 2 : }
1433 : }
1434 :
1435 119 : return true;
1436 : }
1437 :
1438 145 : void AdminServerProvision::loadTransformation(const nlohmann::json &j) {
1439 :
1440 145 : LOGDEBUG(
1441 : std::string msg = ert::tracing::Logger::asString("Loading transformation item: %s", j.dump().c_str()); // avoid newlines in traces (dump(n) pretty print)
1442 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1443 : );
1444 :
1445 : // Transformation object to fill:
1446 145 : auto transformation = std::make_shared<Transformation>();
1447 :
1448 145 : if (transformation->load(j)) {
1449 143 : transformations_.push_back(transformation);
1450 143 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Loaded transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1451 : }
1452 : else {
1453 4 : ert::tracing::Logger::error("Discarded transform item due to incoherent data", ERT_FILE_LOCATION);
1454 : }
1455 145 : }
1456 :
1457 :
1458 : }
1459 : }
1460 :
|