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 242 : AdminServerProvision::AdminServerProvision() : in_state_(DEFAULT_ADMIN_PROVISION_STATE),
72 242 : out_state_(DEFAULT_ADMIN_PROVISION_STATE),
73 363 : response_delay_ms_(0), mock_server_events_data_(nullptr), mock_client_events_data_(nullptr) {;}
74 :
75 :
76 209 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getRequestSchema() {
77 :
78 209 : if(request_schema_id_.empty()) return nullptr;
79 :
80 11 : 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 7 : 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 7 : return request_schema_;
90 : }
91 :
92 104 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getResponseSchema() {
93 :
94 104 : if(response_schema_id_.empty()) return nullptr;
95 :
96 5 : 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 4 : 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 4 : return response_schema_;
106 : }
107 :
108 134 : 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 134 : 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 15 : case Transformation::SourceType::RequestBody:
145 : {
146 15 : if (requestBodyDataPart.isJson()) {
147 12 : std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
148 12 : replaceVariables(path, transformation->getSourcePatterns(), variables, global_variable_);
149 12 : 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 12 : }
157 : else {
158 3 : sourceVault.setString(requestBodyDataPart.str());
159 : }
160 14 : 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 7 : case Transformation::SourceType::Eraser:
189 : {
190 7 : sourceVault.setString(""); // with other than response body nodes, it acts like setting empty string
191 7 : eraser = true;
192 7 : 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 15 : case Transformation::SourceType::SVar:
267 : {
268 15 : std::string varname = transformation->getSource();
269 15 : replaceVariables(varname, transformation->getSourcePatterns(), variables, global_variable_);
270 15 : auto iter = variables.find(varname);
271 15 : 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 10 : break;
280 15 : }
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 50 : case Transformation::SourceType::Value:
298 : {
299 50 : sourceVault.setStringReplacingVariables(transformation->getSource(), transformation->getSourcePatterns(), variables, global_variable_); // replace variables if they exist
300 50 : 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 : }
400 :
401 :
402 121 : return true;
403 : }
404 :
405 25 : bool AdminServerProvision::processFilters(std::shared_ptr<Transformation> transformation,
406 : TypeConverter& sourceVault,
407 : const std::map<std::string, std::string>& variables,
408 : std::smatch &matches,
409 : std::string &source) const
410 : {
411 25 : bool success = false;
412 25 : std::string targetS;
413 25 : std::int64_t targetI = 0;
414 25 : std::uint64_t targetU = 0;
415 25 : double targetF = 0;
416 :
417 : // all the filters except Sum/Multiply, require a string target
418 25 : if (transformation->getFilterType() != Transformation::FilterType::Sum && transformation->getFilterType() != Transformation::FilterType::Multiply) {
419 19 : source = sourceVault.getString(success);
420 19 : if (!success) return false;
421 : }
422 :
423 : // All our regex are built with 'std::regex::optimize' so they are already validated and regex functions cannot throw exception:
424 : //try { // std::regex exceptions
425 25 : switch (transformation->getFilterType()) {
426 3 : case Transformation::FilterType::RegexCapture:
427 : {
428 3 : if (std::regex_match(source, matches, transformation->getFilterRegex()) && matches.size() >=1) {
429 2 : targetS = matches.str(0);
430 2 : sourceVault.setString(targetS);
431 2 : LOGDEBUG(
432 : std::stringstream ss;
433 : ss << "Regex matches: Size = " << matches.size();
434 : for(size_t i=0; i < matches.size(); i++) {
435 : ss << " | [" << i << "] = " << matches.str(i);
436 : }
437 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
438 : );
439 : }
440 : else {
441 1 : LOGDEBUG(
442 : 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());
443 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
444 : );
445 1 : return false;
446 : }
447 2 : break;
448 : }
449 1 : case Transformation::FilterType::RegexReplace:
450 : {
451 1 : targetS = std::regex_replace (source, transformation->getFilterRegex(), transformation->getFilter() /* fmt */);
452 1 : sourceVault.setString(targetS);
453 1 : break;
454 : }
455 1 : case Transformation::FilterType::Append:
456 : {
457 1 : std::string filter = transformation->getFilter();
458 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
459 :
460 1 : targetS = source + filter;
461 1 : sourceVault.setString(targetS);
462 1 : break;
463 1 : }
464 1 : case Transformation::FilterType::Prepend:
465 : {
466 1 : std::string filter = transformation->getFilter();
467 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
468 :
469 1 : targetS = filter + source;
470 1 : sourceVault.setString(targetS);
471 1 : break;
472 1 : }
473 3 : case Transformation::FilterType::Sum:
474 : {
475 3 : switch (transformation->getFilterNumberType()) {
476 1 : case 0: /* integer */
477 : {
478 1 : targetI = sourceVault.getInteger(success);
479 1 : if (success) targetI += transformation->getFilterI();
480 : //else return false; // should not happen (protected by schema)
481 1 : sourceVault.setInteger(targetI);
482 1 : break;
483 : }
484 1 : case 1: /* unsigned */
485 : {
486 1 : targetU = sourceVault.getUnsigned(success);
487 1 : if (success) targetU += transformation->getFilterU();
488 : //else return false; // should not happen (protected by schema)
489 1 : sourceVault.setUnsigned(targetU);
490 1 : break;
491 : }
492 1 : case 2: /* double */
493 : {
494 1 : targetF = sourceVault.getFloat(success);
495 1 : if (success) targetF += transformation->getFilterF();
496 : //else return false; // should not happen (protected by schema)
497 1 : sourceVault.setFloat(targetF);
498 1 : break;
499 : }
500 : }
501 3 : break;
502 : }
503 3 : case Transformation::FilterType::Multiply:
504 : {
505 3 : switch (transformation->getFilterNumberType()) {
506 1 : case 0: /* integer */
507 : {
508 1 : targetI = sourceVault.getInteger(success);
509 1 : if (success) targetI *= transformation->getFilterI();
510 : //else return false; // should not happen (protected by schema)
511 1 : sourceVault.setInteger(targetI);
512 1 : break;
513 : }
514 1 : case 1: /* unsigned */
515 : {
516 1 : targetU = sourceVault.getUnsigned(success);
517 1 : if (success) targetU *= transformation->getFilterU();
518 : //else return false; // should not happen (protected by schema)
519 1 : sourceVault.setUnsigned(targetU);
520 1 : break;
521 : }
522 1 : case 2: /* double */
523 : {
524 1 : targetF = sourceVault.getFloat(success);
525 1 : if (success) targetF *= transformation->getFilterF();
526 : //else return false; // should not happen (protected by schema)
527 1 : sourceVault.setFloat(targetF);
528 1 : break;
529 : }
530 : }
531 3 : break;
532 : }
533 4 : case Transformation::FilterType::ConditionVar: // TODO: if condition is false, source storage could be omitted to improve performance
534 : {
535 : // Get variable value for the variable name 'transformation->getFilter()':
536 4 : std::string varname = transformation->getFilter();
537 4 : bool reverse = (transformation->getFilter()[0] == '!');
538 4 : if (reverse) {
539 2 : varname.erase(0,1);
540 : }
541 4 : auto iter = variables.find(varname);
542 4 : bool varFound = (iter != variables.end());
543 4 : std::string varvalue{};
544 4 : if (varFound) {
545 1 : varvalue = iter->second;
546 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (local)", varname.c_str()), ERT_FILE_LOCATION));
547 : }
548 : else {
549 3 : varFound = global_variable_->tryGet(varname, varvalue);
550 3 : LOGDEBUG(if (varFound) ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (global)", varname.c_str()), ERT_FILE_LOCATION));
551 : }
552 :
553 4 : bool conditionVar = (varFound && !(varvalue.empty()));
554 4 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable value: '%s'", (varFound ? varvalue.c_str():"<undefined>")), ERT_FILE_LOCATION));
555 :
556 4 : if ((reverse && !conditionVar)||(!reverse && conditionVar)) {
557 3 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is true", (reverse ? "!":"")), ERT_FILE_LOCATION));
558 3 : sourceVault.setString(source);
559 : }
560 : else {
561 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is false", (reverse ? "!":"")), ERT_FILE_LOCATION));
562 1 : return false;
563 : }
564 3 : break;
565 8 : }
566 2 : case Transformation::FilterType::EqualTo:
567 : {
568 2 : std::string filter = transformation->getFilter();
569 2 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
570 :
571 : // Get value for the comparison 'transformation->getFilter()':
572 2 : if (source == filter) {
573 1 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is true", ERT_FILE_LOCATION));
574 1 : sourceVault.setString(source);
575 : }
576 : else {
577 1 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is false", ERT_FILE_LOCATION));
578 1 : return false;
579 : }
580 1 : break;
581 2 : }
582 2 : case Transformation::FilterType::DifferentFrom:
583 : {
584 2 : std::string filter = transformation->getFilter();
585 2 : replaceVariables(filter, transformation->getFilterPatterns(), variables, global_variable_);
586 :
587 : // Get value for the comparison 'transformation->getFilter()':
588 2 : if (source != filter) {
589 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is true", ERT_FILE_LOCATION));
590 1 : sourceVault.setString(source);
591 : }
592 : else {
593 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is false", ERT_FILE_LOCATION));
594 1 : return false;
595 : }
596 1 : break;
597 2 : }
598 3 : case Transformation::FilterType::JsonConstraint:
599 : {
600 3 : nlohmann::json sobj = sourceVault.getObject(success);
601 : // should not happen (protected by schema)
602 : //if (!success) {
603 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for JsonConstraint filter must be a valid json object", ERT_FILE_LOCATION));
604 : // return false;
605 : //}
606 3 : std::string failReport;
607 3 : if (h2agent::model::jsonConstraint(sobj, transformation->getFilterObject(), failReport)) {
608 2 : sourceVault.setString("1");
609 : }
610 : else {
611 2 : sourceVault.setString(failReport);
612 : }
613 3 : break;
614 3 : }
615 2 : case Transformation::FilterType::SchemaId:
616 : {
617 2 : nlohmann::json sobj = sourceVault.getObject(success);
618 : // should not happen (protected by schema)
619 : //if (!success) {
620 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for SchemaId filter must be a valid json object", ERT_FILE_LOCATION));
621 : // return false;
622 : //}
623 2 : std::string failReport;
624 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)
625 2 : if (schema) {
626 2 : if (schema->validate(sobj, failReport)) {
627 2 : sourceVault.setString("1");
628 : }
629 : else {
630 1 : sourceVault.setString(failReport);
631 : }
632 : }
633 : else {
634 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);
635 : }
636 2 : break;
637 2 : }
638 : }
639 : //}
640 : //catch (std::exception& e)
641 : //{
642 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
643 : //}
644 :
645 :
646 21 : return true;
647 25 : }
648 :
649 117 : bool AdminServerProvision::processTargets(std::shared_ptr<Transformation> transformation,
650 : TypeConverter &sourceVault,
651 : std::map<std::string, std::string>& variables,
652 : const std::smatch &matches,
653 : bool eraser,
654 : bool hasFilter,
655 : unsigned int &responseStatusCode,
656 : nlohmann::json &responseBodyJson,
657 : std::string &responseBodyAsString,
658 : nghttp2::asio_http2::header_map &responseHeaders,
659 : unsigned int &responseDelayMs,
660 : std::string &outState,
661 : std::string &outStateMethod,
662 : std::string &outStateUri,
663 : bool &breakCondition) const
664 : {
665 117 : bool success = false;
666 117 : std::string targetS;
667 117 : std::int64_t targetI = 0;
668 117 : std::uint64_t targetU = 0;
669 117 : double targetF = 0;
670 117 : bool boolean = false;
671 117 : nlohmann::json obj;
672 :
673 :
674 : try { // nlohmann::json exceptions
675 :
676 117 : std::string target = transformation->getTarget();
677 117 : std::string target2 = transformation->getTarget2(); // foreign outState URI
678 :
679 117 : replaceVariables(target, transformation->getTargetPatterns(), variables, global_variable_);
680 117 : if (!target2.empty()) {
681 3 : replaceVariables(target2, transformation->getTarget2Patterns(), variables, global_variable_);
682 : }
683 :
684 117 : switch (transformation->getTargetType()) {
685 42 : case Transformation::TargetType::ResponseBodyString:
686 : {
687 : // extraction
688 42 : targetS = sourceVault.getString(success);
689 42 : if (!success) return false;
690 : // assignment
691 42 : responseBodyAsString = targetS;
692 42 : break;
693 : }
694 1 : case Transformation::TargetType::ResponseBodyHexString:
695 : {
696 : // extraction
697 1 : targetS = sourceVault.getString(success);
698 1 : if (!success) return false;
699 : // assignment
700 1 : if (!h2agent::model::fromHexString(targetS, responseBodyAsString)) return false;
701 1 : break;
702 : }
703 12 : case Transformation::TargetType::ResponseBodyJson_String:
704 : {
705 : // extraction
706 12 : targetS = sourceVault.getString(success);
707 12 : if (!success) return false;
708 : // assignment
709 12 : nlohmann::json::json_pointer j_ptr(target);
710 11 : responseBodyJson[j_ptr] = targetS;
711 11 : break;
712 11 : }
713 2 : case Transformation::TargetType::ResponseBodyJson_Integer:
714 : {
715 : // extraction
716 2 : targetI = sourceVault.getInteger(success);
717 2 : if (!success) return false;
718 : // assignment
719 2 : nlohmann::json::json_pointer j_ptr(target);
720 2 : responseBodyJson[j_ptr] = targetI;
721 2 : break;
722 2 : }
723 2 : case Transformation::TargetType::ResponseBodyJson_Unsigned:
724 : {
725 : // extraction
726 2 : targetU = sourceVault.getUnsigned(success);
727 2 : if (!success) return false;
728 : // assignment
729 2 : nlohmann::json::json_pointer j_ptr(target);
730 2 : responseBodyJson[j_ptr] = targetU;
731 2 : break;
732 2 : }
733 2 : case Transformation::TargetType::ResponseBodyJson_Float:
734 : {
735 : // extraction
736 2 : targetF = sourceVault.getFloat(success);
737 2 : if (!success) return false;
738 : // assignment
739 2 : nlohmann::json::json_pointer j_ptr(target);
740 2 : responseBodyJson[j_ptr] = targetF;
741 2 : break;
742 2 : }
743 2 : case Transformation::TargetType::ResponseBodyJson_Boolean:
744 : {
745 : // extraction
746 2 : boolean = sourceVault.getBoolean(success);
747 2 : if (!success) return false;
748 : // assignment
749 2 : nlohmann::json::json_pointer j_ptr(target);
750 2 : responseBodyJson[j_ptr] = boolean;
751 2 : break;
752 2 : }
753 18 : case Transformation::TargetType::ResponseBodyJson_Object:
754 : {
755 :
756 18 : if (eraser) {
757 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into json path '%s'", target.c_str()), ERT_FILE_LOCATION));
758 2 : if (target.empty()) {
759 1 : responseBodyJson.erase(responseBodyJson.begin(), responseBodyJson.end());
760 1 : return false;
761 : }
762 :
763 : //erase() DOES NOT SUPPORT JSON POINTERS:
764 : //nlohmann::json::json_pointer j_ptr(target);
765 : //responseBodyJson.erase(j_ptr);
766 : //
767 : // For a path '/a/b/c' we must access to /a/b and then erase "c":
768 1 : size_t lastSlashPos = target.find_last_of("/");
769 : // lastSlashPos will never be std::string::npos here
770 1 : std::string parentPath = target.substr(0, lastSlashPos);
771 1 : std::string childKey = "";
772 1 : if (lastSlashPos + 1 < target.size()) childKey = target.substr(lastSlashPos + 1, target.size());
773 1 : nlohmann::json::json_pointer j_ptr(parentPath);
774 1 : responseBodyJson[j_ptr].erase(childKey);
775 1 : return false;
776 1 : }
777 :
778 : // extraction will be object if possible, falling back to the rest of formats with this priority: string, integer, unsigned, float, boolean
779 : // assignment for valid extraction
780 16 : nlohmann::json::json_pointer j_ptr(target);
781 :
782 : // Native types for SOURCES:
783 : //
784 : // [string] request.uri, request.uri.path, request.header, randomset, strftime, var, globalVar, value, txtFile, binFile, command
785 : // [object] request.body, response.body, serverEvent (when target is also object, it could be promoted to string, unsigned, integer, float or boolean).
786 : // [integer] random, timestamp
787 : // [unsigned] recvseq
788 : // [float] math.*
789 : // [boolean] NONE
790 : // So, depending on the target, the corresponding getter (getInteger, getString, etc.) will be used: WE DO NOT WANT FORCE CONVERSIONS:
791 : // Note that there is not sources with boolean as native type, so boolean getter is never reached (so commented to avoid UT coverage fault).
792 : //
793 16 : switch (sourceVault.getNativeType()) {
794 2 : case TypeConverter::NativeType::Object:
795 2 : obj = sourceVault.getObject(success);
796 2 : if (success) {
797 2 : if (target.empty()) {
798 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
799 : }
800 : else {
801 1 : responseBodyJson[j_ptr] = obj;
802 : }
803 : }
804 2 : break;
805 :
806 6 : case TypeConverter::NativeType::String:
807 6 : targetS = sourceVault.getString(success);
808 6 : if (success) responseBodyJson[j_ptr] = targetS;
809 6 : break;
810 :
811 5 : case TypeConverter::NativeType::Integer:
812 5 : targetI = sourceVault.getInteger(success);
813 5 : if (success) responseBodyJson[j_ptr] = targetI;
814 5 : break;
815 :
816 1 : case TypeConverter::NativeType::Unsigned:
817 1 : targetU = sourceVault.getUnsigned(success);
818 1 : if (success) responseBodyJson[j_ptr] = targetU;
819 1 : break;
820 :
821 1 : case TypeConverter::NativeType::Float:
822 1 : targetF = sourceVault.getFloat(success);
823 1 : if (success) responseBodyJson[j_ptr] = targetF;
824 1 : break;
825 :
826 : // Not reached at the moment:
827 1 : case TypeConverter::NativeType::Boolean:
828 1 : boolean = sourceVault.getBoolean(success);
829 1 : if (success) responseBodyJson[j_ptr] = boolean;
830 1 : break;
831 : }
832 16 : break;
833 16 : }
834 3 : case Transformation::TargetType::ResponseBodyJson_JsonString:
835 : {
836 :
837 : // assignment for valid extraction
838 3 : nlohmann::json::json_pointer j_ptr(target);
839 :
840 : // extraction
841 3 : targetS = sourceVault.getString(success);
842 3 : if (!success) return false;
843 3 : if (!h2agent::model::parseJsonContent(targetS, obj))
844 1 : return false;
845 :
846 : // assignment
847 2 : if (target.empty()) {
848 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
849 : }
850 : else {
851 1 : responseBodyJson[j_ptr] = obj;
852 : }
853 2 : break;
854 3 : }
855 1 : case Transformation::TargetType::ResponseHeader:
856 : {
857 : // extraction
858 1 : targetS = sourceVault.getString(success);
859 1 : if (!success) return false;
860 : // assignment
861 1 : responseHeaders.emplace(target, nghttp2::asio_http2::header_value{targetS});
862 1 : break;
863 : }
864 5 : case Transformation::TargetType::ResponseStatusCode:
865 : {
866 : // extraction
867 5 : targetU = sourceVault.getUnsigned(success);
868 5 : if (!success) return false;
869 : // assignment
870 5 : responseStatusCode = targetU;
871 5 : break;
872 : }
873 1 : case Transformation::TargetType::ResponseDelayMs:
874 : {
875 : // extraction
876 1 : targetU = sourceVault.getUnsigned(success);
877 1 : if (!success) return false;
878 : // assignment
879 1 : responseDelayMs = targetU;
880 1 : break;
881 : }
882 8 : case Transformation::TargetType::TVar:
883 : {
884 8 : if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
885 1 : std::string varname;
886 1 : if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
887 1 : variables[target] = matches.str(0); // variable "as is" stores the entire match
888 4 : for(size_t i=1; i < matches.size(); i++) {
889 3 : varname = target;
890 3 : varname += ".";
891 3 : varname += std::to_string(i);
892 3 : variables[varname] = matches.str(i);
893 3 : LOGDEBUG(
894 : std::stringstream ss;
895 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
896 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
897 : );
898 : }
899 : }
900 1 : }
901 : else {
902 : // extraction
903 7 : targetS = sourceVault.getString(success);
904 7 : if (!success) return false;
905 :
906 7 : if (hasFilter) {
907 4 : if(transformation->getFilterType() == Transformation::FilterType::JsonConstraint) {
908 1 : if (targetS != "1") { // this is a fail report
909 1 : variables[target + ".fail"] = targetS;
910 1 : targetS = "";
911 : }
912 : }
913 3 : else if (transformation->getFilterType() == Transformation::FilterType::SchemaId) {
914 0 : if (targetS != "1") { // this is a fail report
915 0 : variables[target + ".fail"] = targetS;
916 0 : targetS = "";
917 : }
918 : }
919 : }
920 :
921 : // assignment
922 7 : variables[target] = targetS;
923 : }
924 8 : break;
925 : }
926 4 : case Transformation::TargetType::TGVar:
927 : {
928 4 : if (eraser) {
929 : bool exists;
930 1 : global_variable_->remove(target, exists);
931 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));
932 : }
933 3 : else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
934 1 : std::string varname;
935 1 : if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
936 1 : global_variable_->load(target, matches.str(0)); // variable "as is" stores the entire match
937 4 : for(size_t i=1; i < matches.size(); i++) {
938 3 : varname = target;
939 3 : varname += ".";
940 3 : varname += std::to_string(i);
941 3 : global_variable_->load(varname, matches.str(i));
942 3 : LOGDEBUG(
943 : std::stringstream ss;
944 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
945 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
946 : );
947 : }
948 : }
949 1 : }
950 : else {
951 : // extraction
952 2 : targetS = sourceVault.getString(success);
953 2 : if (!success) return false;
954 : // assignment
955 2 : global_variable_->load(target, targetS);
956 : }
957 4 : break;
958 : }
959 4 : case Transformation::TargetType::OutState:
960 : {
961 : // extraction
962 4 : targetS = sourceVault.getString(success);
963 4 : if (!success) return false;
964 : // assignments
965 4 : outState = targetS;
966 4 : outStateMethod = target; // empty on regular usage
967 4 : outStateUri = target2; // empty on regular usage
968 4 : break;
969 : }
970 2 : case Transformation::TargetType::TTxtFile:
971 : {
972 : // extraction
973 2 : targetS = sourceVault.getString(success);
974 2 : if (!success) return false;
975 :
976 2 : if (eraser) {
977 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into text file '%s'", target.c_str()), ERT_FILE_LOCATION));
978 1 : file_manager_->empty(target/*path*/);
979 : }
980 : else {
981 : // assignments
982 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
983 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
984 : // files type.
985 1 : file_manager_->write(target/*path*/, targetS/*data*/, true/*text*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
986 : }
987 2 : break;
988 : }
989 2 : case Transformation::TargetType::TBinFile:
990 : {
991 : // extraction
992 2 : targetS = sourceVault.getString(success);
993 2 : if (!success) return false;
994 :
995 2 : if (eraser) {
996 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into binary file '%s'", target.c_str()), ERT_FILE_LOCATION));
997 1 : file_manager_->empty(target/*path*/);
998 : }
999 : else {
1000 : // assignments
1001 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
1002 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
1003 : // files type.
1004 1 : file_manager_->write(target/*path*/, targetS/*data*/, false/*binary*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
1005 : }
1006 2 : break;
1007 : }
1008 1 : case Transformation::TargetType::UDPSocket:
1009 : {
1010 : // extraction
1011 1 : targetS = sourceVault.getString(success);
1012 1 : if (!success) return false;
1013 :
1014 : // assignments
1015 : // Possible delay provided in 'target': <path>|<delay>
1016 1 : std::string path = target;
1017 1 : size_t lastDotPos = target.find_last_of("|");
1018 1 : unsigned int delayMs = atoi(target.substr(lastDotPos + 1).c_str());
1019 1 : path = target.substr(0, lastDotPos);
1020 :
1021 1 : LOGDEBUG(
1022 : std::string msg = ert::tracing::Logger::asString("UDPSocket '%s' target, delayed %u milliseconds, in transformation item", path.c_str(), delayMs);
1023 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1024 : );
1025 :
1026 1 : socket_manager_->write(path, targetS/*data*/, delayMs * 1000 /* usecs */);
1027 1 : break;
1028 1 : }
1029 3 : case Transformation::TargetType::ServerEventToPurge:
1030 : {
1031 3 : if (!eraser) {
1032 1 : LOGDEBUG(ert::tracing::Logger::debug("'ServerEventToPurge' target type only works with 'eraser' source type. This transformation will be ignored.", ERT_FILE_LOCATION));
1033 2 : return false;
1034 : }
1035 : // transformation->getTargetTokenized() is a vector:
1036 : //
1037 : // requestMethod: index 0
1038 : // requestUri: index 1
1039 : // eventNumber: index 2
1040 2 : std::string event_method = transformation->getTargetTokenized()[0];
1041 2 : replaceVariables(event_method, transformation->getTargetPatterns(), variables, global_variable_);
1042 2 : std::string event_uri = transformation->getTargetTokenized()[1];
1043 2 : replaceVariables(event_uri, transformation->getTargetPatterns(), variables, global_variable_);
1044 2 : std::string event_number = transformation->getTargetTokenized()[2];
1045 2 : replaceVariables(event_number, transformation->getTargetPatterns(), variables, global_variable_);
1046 :
1047 2 : bool serverDataDeleted = false;
1048 2 : EventKey ekey(event_method, event_uri, event_number);
1049 2 : bool success = mock_server_events_data_->clear(serverDataDeleted, ekey);
1050 :
1051 2 : if (!success) {
1052 1 : LOGDEBUG(
1053 : std::string msg = ert::tracing::Logger::asString("Unexpected error while removing server data event '%s' in transformation item", transformation->getTarget().c_str());
1054 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1055 : );
1056 1 : return false;
1057 : }
1058 :
1059 1 : LOGDEBUG(
1060 : std::string msg = ert::tracing::Logger::asString("Server event '%s' removal result: %s", transformation->getTarget().c_str(), (serverDataDeleted ? "SUCCESS":"NOTHING REMOVED"));
1061 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1062 : );
1063 1 : break;
1064 8 : }
1065 2 : case Transformation::TargetType::Break:
1066 : {
1067 : // extraction
1068 2 : targetS = sourceVault.getString(success);
1069 2 : if (!success) return false;
1070 : // assignments
1071 2 : if (targetS.empty()) {
1072 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action ignored (empty string as source provided)", ERT_FILE_LOCATION));
1073 1 : return false;
1074 : }
1075 :
1076 1 : breakCondition = true;
1077 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action triggered: ignoring remaining transformation items", ERT_FILE_LOCATION));
1078 1 : return false;
1079 : break;
1080 : }
1081 : // this won't happen due to schema for server target types:
1082 0 : case Transformation::TargetType::RequestBodyString:
1083 : case Transformation::TargetType::RequestBodyHexString:
1084 : case Transformation::TargetType::RequestBodyJson_String:
1085 : case Transformation::TargetType::RequestBodyJson_Integer:
1086 : case Transformation::TargetType::RequestBodyJson_Unsigned:
1087 : case Transformation::TargetType::RequestBodyJson_Float:
1088 : case Transformation::TargetType::RequestBodyJson_Boolean:
1089 : case Transformation::TargetType::RequestBodyJson_Object:
1090 : case Transformation::TargetType::RequestBodyJson_JsonString:
1091 0 : break;
1092 : }
1093 125 : }
1094 1 : catch (std::exception& e)
1095 : {
1096 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1097 1 : }
1098 :
1099 :
1100 110 : return true;
1101 117 : }
1102 :
1103 103 : void AdminServerProvision::transform( const std::string &requestUri,
1104 : const std::string &requestUriPath,
1105 : const std::map<std::string, std::string> &requestQueryParametersMap,
1106 : DataPart &requestBodyDataPart,
1107 : const nghttp2::asio_http2::header_map &requestHeaders,
1108 : std::uint64_t generalUniqueServerSequence,
1109 :
1110 : /* OUTPUT PARAMETERS WHICH ALREADY HAVE DEFAULT VALUES BEFORE TRANSFORMATIONS: */
1111 : unsigned int &responseStatusCode,
1112 : nghttp2::asio_http2::header_map &responseHeaders,
1113 : std::string &responseBody,
1114 : unsigned int &responseDelayMs,
1115 : std::string &outState,
1116 : std::string &outStateMethod,
1117 : std::string &outStateUri
1118 : )
1119 : {
1120 : // Default values without transformations:
1121 103 : responseStatusCode = getResponseCode();
1122 103 : responseHeaders = getResponseHeaders();
1123 103 : responseDelayMs = getResponseDelayMilliseconds();
1124 103 : outState = getOutState(); // prepare next request state, with URI path before transformed with matching algorithms
1125 103 : outStateMethod = "";
1126 103 : outStateUri = "";
1127 :
1128 : // Check if the request body must be decoded:
1129 103 : bool mustDecodeRequestBody = false;
1130 103 : if (getRequestSchema()) {
1131 2 : mustDecodeRequestBody = true;
1132 : }
1133 : else {
1134 219 : for (auto it = transformations_.begin(); it != transformations_.end(); it ++) {
1135 133 : if ((*it)->getSourceType() == Transformation::SourceType::RequestBody) {
1136 15 : if (!requestBodyDataPart.str().empty()) {
1137 14 : mustDecodeRequestBody = true;
1138 : }
1139 : else {
1140 1 : LOGINFORMATIONAL(ert::tracing::Logger::informational("Empty request body received: some transformations will be ignored", ERT_FILE_LOCATION));
1141 : }
1142 15 : break;
1143 : }
1144 : }
1145 : }
1146 103 : if (mustDecodeRequestBody) {
1147 16 : requestBodyDataPart.decode(requestHeaders);
1148 : }
1149 :
1150 : // Request schema validation (normally used to validate native json received, but can also be used to validate the agent json representation (multipart, text, etc.)):
1151 103 : if (getRequestSchema()) {
1152 2 : std::string error{};
1153 2 : if (!getRequestSchema()->validate(requestBodyDataPart.getJson(), error)) {
1154 1 : responseStatusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
1155 1 : return; // INTERRUPT TRANSFORMATIONS
1156 : }
1157 2 : }
1158 :
1159 : // Find out if response body will need to be cloned (this is true if any transformation uses it as target):
1160 102 : bool usesResponseBodyAsTransformationJsonTarget = false;
1161 188 : for (auto it = transformations_.begin(); it != transformations_.end(); it ++) {
1162 231 : if ((*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_String ||
1163 222 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_Integer ||
1164 218 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_Unsigned ||
1165 214 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_Float ||
1166 210 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_Boolean ||
1167 335 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_Object ||
1168 89 : (*it)->getTargetType() == Transformation::TargetType::ResponseBodyJson_JsonString) {
1169 33 : usesResponseBodyAsTransformationJsonTarget = true;
1170 33 : break;
1171 : }
1172 : }
1173 :
1174 102 : nlohmann::json responseBodyJson;
1175 102 : if (usesResponseBodyAsTransformationJsonTarget) {
1176 33 : responseBodyJson = getResponseBody(); // clone provision response body to manipulate this copy and finally we will dump() it over 'responseBody':
1177 : // if(usesResponseBodyAsTransformationJsonTarget) responseBody = responseBodyJson.dump(); <--- place this after transformations (*)
1178 : }
1179 : else {
1180 69 : responseBody = getResponseBodyAsString(); // this could be overwritten by targets ResponseBodyString or ResponseBodyHexString
1181 : }
1182 :
1183 : // Dynamic variables map: inherited along the transformation chain
1184 102 : std::map<std::string, std::string> variables; // source & target variables (key=variable name/value=variable value)
1185 :
1186 : // Type converter:
1187 102 : TypeConverter sourceVault{};
1188 :
1189 : // Apply transformations sequentially
1190 102 : bool breakCondition = false;
1191 236 : for (auto it = transformations_.begin(); it != transformations_.end(); it ++) {
1192 :
1193 135 : if (breakCondition) break;
1194 :
1195 134 : auto transformation = (*it);
1196 134 : bool eraser = false;
1197 :
1198 134 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Processing transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1199 :
1200 : // SOURCES: RequestUri, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser, Math, Random, Timestamp, Strftime, Recvseq, SVar, SGvar, Value, ServerEvent, InState
1201 134 : if (!processSources(transformation, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, eraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) {
1202 13 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on source", ERT_FILE_LOCATION));
1203 13 : continue;
1204 : }
1205 :
1206 121 : std::smatch matches; // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
1207 : // So, we can't use 'matches' as container because source may change: BUT, using that source exclusively, it will work (*)
1208 121 : std::string source; // Now, this never will be out of scope, and 'matches' will be valid.
1209 :
1210 : // FILTERS: RegexCapture, RegexReplace, Append, Prepend, Sum, Multiply, ConditionVar, EqualTo, DifferentFrom, JsonConstraint, SchemaId
1211 121 : bool hasFilter = transformation->hasFilter();
1212 121 : if (hasFilter) {
1213 25 : if (eraser || !processFilters(transformation, sourceVault, variables, matches, source)) {
1214 4 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on filter", ERT_FILE_LOCATION));
1215 4 : if (eraser) LOGWARNING(ert::tracing::Logger::warning("Filter is not allowed when using 'eraser' source type. Transformation will be ignored.", ERT_FILE_LOCATION));
1216 4 : continue;
1217 : }
1218 : }
1219 :
1220 : // 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
1221 117 : if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, breakCondition)) {
1222 7 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
1223 7 : continue;
1224 : }
1225 :
1226 156 : }
1227 :
1228 : // (*) Regenerate final responseBody after transformations:
1229 102 : if(usesResponseBodyAsTransformationJsonTarget && !responseBodyJson.empty()) {
1230 : try {
1231 32 : responseBody = responseBodyJson.dump(); // this may arise type error, for example in case of trying to set json field value with binary data:
1232 : // When having a provision transformation from 'request.body' to 'response.body.json.string./whatever':
1233 : // echo -en '\x80\x01' | curl --http2-prior-knowledge -i -H 'content-type:application/octet-stream' -X GET "<traffic url>/uri" --data-binary @-
1234 : //
1235 : // This is not valid and must be protected. The user should use another kind of target to store binary.
1236 : }
1237 1 : catch (const std::exception& e)
1238 : {
1239 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1240 1 : }
1241 : }
1242 :
1243 : // Response schema validation (not supported for response body created by non-json targets, to simplify the fact to parse need on ResponseBodyString/ResponseBodyHexString):
1244 102 : if (getResponseSchema()) {
1245 1 : std::string error{};
1246 1 : if (!getResponseSchema()->validate(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), error)) {
1247 1 : responseStatusCode = ert::http2comm::ResponseCode::INTERNAL_SERVER_ERROR; // 500: built response will be anyway sent although status code is overwritten with internal server error.
1248 : }
1249 1 : }
1250 102 : }
1251 :
1252 121 : bool AdminServerProvision::load(const nlohmann::json &j, bool regexMatchingConfigured) {
1253 :
1254 : // Store whole document (useful for GET operation)
1255 121 : json_ = j;
1256 :
1257 : // Mandatory
1258 121 : auto requestMethod_it = j.find("requestMethod");
1259 121 : request_method_ = *requestMethod_it;
1260 :
1261 121 : auto it = j.find("responseCode");
1262 121 : response_code_ = *it;
1263 :
1264 : // Optional
1265 121 : it = j.find("requestUri");
1266 121 : if (it != j.end() && it->is_string()) {
1267 121 : request_uri_ = *it;
1268 : }
1269 :
1270 121 : it = j.find("inState");
1271 121 : if (it != j.end() && it->is_string()) {
1272 1 : in_state_ = *it;
1273 1 : if (in_state_.empty()) in_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1274 : }
1275 :
1276 121 : it = j.find("outState");
1277 121 : if (it != j.end() && it->is_string()) {
1278 3 : out_state_ = *it;
1279 3 : if (out_state_.empty()) out_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1280 : }
1281 :
1282 121 : it = j.find("requestSchemaId");
1283 121 : if (it != j.end() && it->is_string()) {
1284 11 : request_schema_id_ = *it;
1285 11 : if (request_schema_id_.empty()) {
1286 1 : ert::tracing::Logger::error("Invalid empty request schema identifier", ERT_FILE_LOCATION);
1287 1 : return false;
1288 : }
1289 : }
1290 :
1291 120 : it = j.find("responseSchemaId");
1292 120 : if (it != j.end() && it->is_string()) {
1293 11 : response_schema_id_ = *it;
1294 11 : if (response_schema_id_.empty()) {
1295 1 : ert::tracing::Logger::error("Invalid empty response schema identifier", ERT_FILE_LOCATION);
1296 1 : return false;
1297 : }
1298 : }
1299 :
1300 119 : it = j.find("responseHeaders");
1301 119 : if (it != j.end() && it->is_object()) {
1302 333 : for (auto& [key, val] : it->items())
1303 333 : response_headers_.emplace(key, nghttp2::asio_http2::header_value{val});
1304 : }
1305 :
1306 119 : it = j.find("responseBody");
1307 119 : if (it != j.end()) {
1308 111 : if (it->is_object() || it->is_array()) {
1309 105 : response_body_ = *it;
1310 105 : response_body_string_ = response_body_.dump(); // valid as cache for static responses (not updated with transformations)
1311 : }
1312 6 : else if (it->is_string()) {
1313 1 : response_body_string_ = *it;
1314 : }
1315 5 : else if (it->is_number_integer() || it->is_number_unsigned()) {
1316 : //response_body_integer_ = *it;
1317 2 : int number = *it;
1318 2 : response_body_string_ = std::to_string(number);
1319 : }
1320 3 : else if (it->is_number_float()) {
1321 : //response_body_number_ = *it;
1322 1 : response_body_string_ = std::to_string(double(*it));
1323 : }
1324 2 : else if (it->is_boolean()) {
1325 : //response_body_boolean_ = *it;
1326 1 : response_body_string_ = ((bool)(*it) ? "true":"false");
1327 : }
1328 1 : else if (it->is_null()) {
1329 : //response_body_null_ = true;
1330 1 : response_body_string_ = "null";
1331 : }
1332 : }
1333 :
1334 119 : it = j.find("responseDelayMs");
1335 119 : if (it != j.end() && it->is_number()) {
1336 98 : response_delay_ms_ = *it;
1337 : }
1338 :
1339 119 : auto transform_it = j.find("transform");
1340 119 : if (transform_it != j.end()) {
1341 246 : for (auto it : *transform_it) { // "it" is of type json::reference and has no key() member
1342 146 : loadTransformation(it);
1343 146 : }
1344 : }
1345 :
1346 : // Store key:
1347 119 : h2agent::model::calculateStringKey(key_, in_state_, request_method_, request_uri_);
1348 :
1349 119 : if (regexMatchingConfigured) {
1350 : // Precompile regex with key, only for 'RegexMatching' algorithm:
1351 : try {
1352 6 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Assigning regex: %s", key_.c_str()), ERT_FILE_LOCATION));
1353 6 : regex_.assign(key_, std::regex::optimize);
1354 : }
1355 2 : catch (std::regex_error &e) {
1356 4 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1357 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);
1358 2 : return false;
1359 2 : }
1360 : }
1361 :
1362 117 : return true;
1363 : }
1364 :
1365 146 : void AdminServerProvision::loadTransformation(const nlohmann::json &j) {
1366 :
1367 146 : LOGDEBUG(
1368 : std::string msg = ert::tracing::Logger::asString("Loading transformation item: %s", j.dump().c_str()); // avoid newlines in traces (dump(n) pretty print)
1369 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1370 : );
1371 :
1372 : // Transformation object to fill:
1373 146 : auto transformation = std::make_shared<Transformation>();
1374 :
1375 146 : if (transformation->load(j)) {
1376 144 : transformations_.push_back(transformation);
1377 144 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Loaded transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1378 : }
1379 : else {
1380 4 : ert::tracing::Logger::error("Discarded transform item due to incoherent data", ERT_FILE_LOCATION);
1381 : }
1382 146 : }
1383 :
1384 :
1385 : }
1386 : }
1387 :
|