LCOV - code coverage report
Current view: top level - model - AdminServerProvision.cpp (source / functions) Coverage Total Hit
Test: final-coverage.info Lines: 99.1 % 702 696
Test Date: 2025-12-03 20:05:55 Functions: 100.0 % 9 9

            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              : 
        

Generated by: LCOV version 2.0-1