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

Generated by: LCOV version 2.0-1