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 <Vault.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 342 : AdminServerProvision::AdminServerProvision() : in_state_(DEFAULT_ADMIN_PROVISION_STATE),
72 342 : out_state_(DEFAULT_ADMIN_PROVISION_STATE),
73 513 : response_delay_ms_(0), mock_server_events_data_(nullptr), mock_client_events_data_(nullptr) {;}
74 :
75 :
76 309 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getRequestSchema() {
77 :
78 309 : if(request_schema_id_.empty()) return nullptr;
79 :
80 7 : if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
81 6 : if (request_schema_) return request_schema_; // provision cache
82 2 : request_schema_ = admin_data_->getSchemaData().find(request_schema_id_);
83 : }
84 :
85 3 : LOGWARNING(
86 : if (!request_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for incoming message: VALIDATION will be IGNORED", request_schema_id_.c_str()), ERT_FILE_LOCATION);
87 : );
88 :
89 3 : return request_schema_;
90 : }
91 :
92 154 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getResponseSchema() {
93 :
94 154 : if(response_schema_id_.empty()) return nullptr;
95 :
96 3 : if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
97 2 : if (response_schema_) return response_schema_; // provision cache
98 1 : response_schema_ = admin_data_->getSchemaData().find(response_schema_id_);
99 : }
100 :
101 2 : LOGWARNING(
102 : if (!response_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for outgoing message: VALIDATION will be IGNORED", response_schema_id_.c_str()), ERT_FILE_LOCATION);
103 : );
104 :
105 2 : return response_schema_;
106 : }
107 :
108 212 : 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 212 : 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 22 : case Transformation::SourceType::RequestBody:
145 : {
146 22 : if (requestBodyDataPart.isJson()) {
147 19 : std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
148 19 : replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
149 19 : 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 19 : }
157 : else {
158 3 : sourceVault.setString(requestBodyDataPart.str());
159 : }
160 21 : 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, vault_);
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 2 : case Transformation::SourceType::RequestHeaders:
189 : {
190 2 : nlohmann::json arr = nlohmann::json::array();
191 50 : for (const auto &h : requestHeaders) arr.push_back({{"name", h.first}, {"value", h.second.value}});
192 2 : sourceVault.setObject(arr, "");
193 2 : break;
194 2 : }
195 0 : case Transformation::SourceType::ResponseHeaders:
196 : {
197 : // Not available in server provision (no response received yet)
198 0 : return false;
199 : }
200 8 : case Transformation::SourceType::Eraser:
201 : {
202 8 : eraser = true;
203 8 : break;
204 : }
205 3 : case Transformation::SourceType::Math:
206 : {
207 3 : std::string expressionString = transformation->getSource();
208 3 : replaceVariables(expressionString, transformation->getSourcePatterns(), variables, vault_);
209 :
210 : /*
211 : We don't use builtin variables as we can parse h2agent ones which is easier to implement:
212 :
213 : typedef exprtk::symbol_table<double> symbol_table_t;
214 : symbol_table_t symbol_table;
215 : double x = 2.0;
216 : symbol_table.add_variable("x",x);
217 : expression.register_symbol_table(symbol_table);
218 : parser.compile("3*x",expression);
219 : std::cout << expression.value() << std::endl; // 3*2
220 : */
221 :
222 3 : expression_t expression;
223 3 : parser_t parser;
224 3 : parser.compile(expressionString, expression);
225 :
226 3 : double result = expression.value(); // if the result has decimals, set as float. If not, set as integer:
227 3 : if (result == (int)result) sourceVault.setInteger(expression.value());
228 1 : else sourceVault.setFloat(expression.value());
229 3 : break;
230 3 : }
231 2 : case Transformation::SourceType::Random:
232 : {
233 2 : int range = transformation->getSourceI2() - transformation->getSourceI1() + 1;
234 2 : sourceVault.setInteger(transformation->getSourceI1() + (rand() % range));
235 2 : break;
236 : }
237 1 : case Transformation::SourceType::RandomSet:
238 : {
239 1 : sourceVault.setStringReplacingVariables(transformation->getSourceTokenized()[rand () % transformation->getSourceTokenized().size()], transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
240 1 : break;
241 : }
242 4 : case Transformation::SourceType::Timestamp:
243 : {
244 4 : if (transformation->getSource() == "s") {
245 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
246 : }
247 3 : else if (transformation->getSource() == "ms") {
248 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
249 : }
250 2 : else if (transformation->getSource() == "us") {
251 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
252 : }
253 1 : else if (transformation->getSource() == "ns") {
254 1 : sourceVault.setInteger(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
255 : }
256 4 : break;
257 : }
258 1 : case Transformation::SourceType::Strftime:
259 : {
260 1 : std::time_t unixTime = 0;
261 1 : std::time (&unixTime);
262 1 : char buffer[100] = {0};
263 1 : /*size_t size = */strftime(buffer, sizeof(buffer), transformation->getSource().c_str(), localtime(&unixTime));
264 : //if (size > 1) { // convert TZ offset to RFC3339 format
265 : // char minute[] = { buffer[size-2], buffer[size-1], '\0' };
266 : // sprintf(buffer + size - 2, ":%s", minute);
267 : //}
268 :
269 2 : sourceVault.setStringReplacingVariables(std::string(buffer), transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
270 1 : break;
271 : }
272 2 : case Transformation::SourceType::Recvseq:
273 : {
274 2 : sourceVault.setUnsigned(generalUniqueServerSequence);
275 2 : break;
276 : }
277 27 : case Transformation::SourceType::SVar:
278 : {
279 27 : std::string varname = transformation->getSource();
280 27 : replaceVariables(varname, transformation->getSourcePatterns(), variables, vault_);
281 27 : auto iter = variables.find(varname);
282 27 : if (iter != variables.end()) sourceVault.setString(iter->second);
283 : else {
284 6 : LOGDEBUG(
285 : std::string msg = ert::tracing::Logger::asString("Unable to extract source variable '%s' in transformation item", varname.c_str());
286 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
287 : );
288 6 : return false;
289 : }
290 21 : break;
291 27 : }
292 31 : case Transformation::SourceType::SGVar:
293 : {
294 31 : std::string varname = transformation->getSource();
295 31 : replaceVariables(varname, transformation->getSourcePatterns(), variables, vault_);
296 31 : nlohmann::json vaultValue{};
297 31 : bool exists = vault_->tryGet(varname, vaultValue);
298 31 : if (exists) {
299 27 : std::string path = transformation->getSource2();
300 27 : if (!path.empty()) replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
301 27 : if (!sourceVault.setObject(vaultValue, path)) {
302 1 : LOGDEBUG(
303 : std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from vault entry '%s' in transformation item", path.c_str(), varname.c_str());
304 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
305 : );
306 1 : return false;
307 : }
308 27 : }
309 : else {
310 4 : LOGDEBUG(
311 : std::string msg = ert::tracing::Logger::asString("Unable to extract source vault entry '%s' in transformation item", varname.c_str());
312 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
313 : );
314 4 : return false;
315 : }
316 26 : break;
317 62 : }
318 81 : case Transformation::SourceType::Value:
319 : {
320 81 : sourceVault.setStringReplacingVariables(transformation->getSource(), transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
321 81 : break;
322 : }
323 3 : case Transformation::SourceType::ServerEvent:
324 : {
325 : // transformation->getSourceTokenized() is a vector:
326 : //
327 : // requestMethod: index 0
328 : // requestUri: index 1
329 : // eventNumber: index 2
330 : // eventPath: index 3
331 : // recvseq: index 4
332 3 : std::string event_method = transformation->getSourceTokenized()[0];
333 3 : replaceVariables(event_method, transformation->getSourcePatterns(), variables, vault_);
334 3 : std::string event_uri = transformation->getSourceTokenized()[1];
335 3 : replaceVariables(event_uri, transformation->getSourcePatterns(), variables, vault_);
336 3 : std::string event_number = transformation->getSourceTokenized()[2];
337 3 : replaceVariables(event_number, transformation->getSourcePatterns(), variables, vault_);
338 3 : std::string event_path = transformation->getSourceTokenized()[3];
339 3 : replaceVariables(event_path, transformation->getSourcePatterns(), variables, vault_);
340 3 : std::string event_recvseq = transformation->getSourceTokenized()[4];
341 3 : replaceVariables(event_recvseq, transformation->getSourcePatterns(), variables, vault_);
342 :
343 : // Now, access the server data for the former selection values:
344 3 : nlohmann::json object;
345 3 : std::shared_ptr<MockEvent> mockServerRequest;
346 :
347 3 : if (!event_recvseq.empty()) {
348 : try {
349 0 : DataKey dkey(event_method, event_uri);
350 0 : mockServerRequest = mock_server_events_data_->getEventByRecvSeq(dkey, (std::uint64_t)std::stoull(event_recvseq));
351 0 : }
352 0 : catch (const std::exception&) { return false; }
353 : }
354 : else {
355 3 : EventKey ekey(event_method, event_uri, event_number);
356 3 : mockServerRequest = mock_server_events_data_->getEvent(ekey);
357 3 : }
358 :
359 3 : if (!mockServerRequest) {
360 1 : LOGDEBUG(
361 : std::string msg = ert::tracing::Logger::asString("Unable to extract server event for variable '%s' in transformation item", transformation->getSource().c_str());
362 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
363 : );
364 1 : return false;
365 : }
366 :
367 6 : if (!sourceVault.setObject(mockServerRequest->getJson(), event_path /* document path (empty or not to be whole 'requests number' or node) */)) {
368 1 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Cannot extract path '%s' from server event for source '%s'", event_path.c_str(), transformation->getSource().c_str()), ERT_FILE_LOCATION);
369 1 : return false;
370 : }
371 :
372 1 : LOGDEBUG(
373 : std::string msg = ert::tracing::Logger::asString("Extracted object from server event: %s", sourceVault.asString().c_str());
374 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
375 : );
376 1 : break;
377 21 : }
378 3 : case Transformation::SourceType::InState:
379 : {
380 3 : sourceVault.setString(getInState());
381 3 : break;
382 : }
383 2 : case Transformation::SourceType::STxtFile:
384 : {
385 2 : std::string path = transformation->getSource();
386 2 : replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
387 :
388 2 : std::string content;
389 2 : file_manager_->read(path, content, true/*text*/);
390 2 : sourceVault.setString(std::move(content));
391 2 : break;
392 2 : }
393 2 : case Transformation::SourceType::SBinFile:
394 : {
395 2 : std::string path = transformation->getSource();
396 2 : replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
397 :
398 2 : std::string content;
399 2 : file_manager_->read(path, content, false/*binary*/);
400 2 : sourceVault.setString(std::move(content));
401 2 : break;
402 2 : }
403 2 : case Transformation::SourceType::Command:
404 : {
405 2 : std::string command = transformation->getSource();
406 2 : replaceVariables(command, transformation->getSourcePatterns(), variables, vault_);
407 :
408 : static char buffer[256];
409 2 : std::string output{};
410 :
411 2 : FILE *fp = popen(command.c_str(), "r");
412 2 : variables["rc"] = "-1"; // rare case where fp could be NULL
413 2 : if (fp) {
414 : /* This makes asyncronous the command execution, but we will have broken pipe and cannot capture anything.
415 : // fgets is blocking (https://stackoverflow.com/questions/6055702/using-fgets-as-non-blocking-function-c/6055774#6055774)
416 : int fd = fileno(fp);
417 : int flags = fcntl(fd, F_GETFL, 0);
418 : flags |= O_NONBLOCK;
419 : fcntl(fd, F_SETFL, flags);
420 : */
421 :
422 3 : while(fgets(buffer, sizeof(buffer), fp))
423 : {
424 1 : output += buffer;
425 : }
426 6 : variables["rc"] = std::to_string(WEXITSTATUS(/* status = */pclose(fp))); // rc = status >>= 8; // divide by 256
427 : }
428 :
429 2 : sourceVault.setString(std::move(output));
430 2 : break;
431 2 : }
432 3 : case Transformation::SourceType::ClientEvent:
433 : {
434 : // transformation->getSourceTokenized() is a vector:
435 : //
436 : // clientEndpointId: index 0
437 : // requestMethod: index 1
438 : // requestUri: index 2
439 : // eventNumber: index 3
440 : // eventPath: index 4
441 : // sendseq: index 5
442 3 : std::string event_endpoint = transformation->getSourceTokenized()[0];
443 3 : replaceVariables(event_endpoint, transformation->getSourcePatterns(), variables, vault_);
444 3 : std::string event_method = transformation->getSourceTokenized()[1];
445 3 : replaceVariables(event_method, transformation->getSourcePatterns(), variables, vault_);
446 3 : std::string event_uri = transformation->getSourceTokenized()[2];
447 3 : replaceVariables(event_uri, transformation->getSourcePatterns(), variables, vault_);
448 3 : std::string event_number = transformation->getSourceTokenized()[3];
449 3 : replaceVariables(event_number, transformation->getSourcePatterns(), variables, vault_);
450 3 : std::string event_path = transformation->getSourceTokenized()[4];
451 3 : replaceVariables(event_path, transformation->getSourcePatterns(), variables, vault_);
452 3 : std::string event_sendseq = transformation->getSourceTokenized()[5];
453 3 : replaceVariables(event_sendseq, transformation->getSourcePatterns(), variables, vault_);
454 :
455 3 : DataKey dkey(event_endpoint, event_method, event_uri);
456 3 : std::shared_ptr<MockEvent> mockClientRequest;
457 :
458 3 : if (!event_sendseq.empty()) {
459 : try {
460 0 : mockClientRequest = mock_client_events_data_->getEventBySendSeq(dkey, (std::uint64_t)std::stoull(event_sendseq));
461 : }
462 0 : catch (const std::exception&) { return false; }
463 : }
464 : else {
465 3 : EventKey ekey(dkey, event_number);
466 3 : mockClientRequest = mock_client_events_data_->getEvent(ekey);
467 3 : }
468 :
469 3 : if (!mockClientRequest) {
470 1 : LOGDEBUG(
471 : std::string msg = ert::tracing::Logger::asString("Unable to extract client event for variable '%s' in transformation item", transformation->getSource().c_str());
472 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
473 : );
474 1 : return false;
475 : }
476 :
477 6 : if (!sourceVault.setObject(mockClientRequest->getJson(), event_path)) {
478 1 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Cannot extract path '%s' from client event for source '%s'", event_path.c_str(), transformation->getSource().c_str()), ERT_FILE_LOCATION);
479 1 : return false;
480 : }
481 :
482 1 : LOGDEBUG(
483 : std::string msg = ert::tracing::Logger::asString("Extracted object from client event: %s", sourceVault.asString().c_str());
484 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
485 : );
486 1 : break;
487 24 : }
488 : // Not applicable in server context:
489 0 : case Transformation::SourceType::Sendseq:
490 : case Transformation::SourceType::ResponseHeader:
491 : case Transformation::SourceType::ResponseStatusCode:
492 0 : return false;
493 : }
494 :
495 :
496 193 : return true;
497 36 : }
498 :
499 60 : bool AdminServerProvision::processFilters(std::shared_ptr<Transformation> transformation,
500 : TypeConverter& sourceVault,
501 : const std::map<std::string, std::string>& variables,
502 : std::smatch &matches,
503 : std::string &source) const
504 : {
505 60 : bool success = false;
506 60 : std::string targetS;
507 60 : std::int64_t targetI = 0;
508 60 : std::uint64_t targetU = 0;
509 60 : double targetF = 0;
510 :
511 : // all the filters except Sum/Multiply/Strftime/RegexKey/Size, require a string target
512 60 : if (transformation->getFilterType() != Transformation::FilterType::Sum && transformation->getFilterType() != Transformation::FilterType::Multiply && transformation->getFilterType() != Transformation::FilterType::FStrftime && transformation->getFilterType() != Transformation::FilterType::RegexKey && transformation->getFilterType() != Transformation::FilterType::Size) {
513 39 : source = sourceVault.getString(success);
514 39 : if (!success) return false;
515 : }
516 :
517 : // All our regex are built with 'std::regex::optimize' so they are already validated and regex functions cannot throw exception:
518 : //try { // std::regex exceptions
519 60 : switch (transformation->getFilterType()) {
520 3 : case Transformation::FilterType::RegexCapture:
521 : {
522 3 : if (std::regex_match(source, matches, transformation->getFilterRegex()) && matches.size() >=1) {
523 2 : targetS = matches.str(0);
524 2 : sourceVault.setString(targetS);
525 2 : LOGDEBUG(
526 : std::stringstream ss;
527 : ss << "Regex matches: Size = " << matches.size();
528 : for(size_t i=0; i < matches.size(); i++) {
529 : ss << " | [" << i << "] = " << matches.str(i);
530 : }
531 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
532 : );
533 : }
534 : else {
535 1 : LOGDEBUG(
536 : 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());
537 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
538 : );
539 1 : return false;
540 : }
541 2 : break;
542 : }
543 1 : case Transformation::FilterType::RegexReplace:
544 : {
545 1 : targetS = std::regex_replace (source, transformation->getFilterRegex(), transformation->getFilter() /* fmt */);
546 1 : sourceVault.setString(targetS);
547 1 : break;
548 : }
549 1 : case Transformation::FilterType::Append:
550 : {
551 1 : std::string filter = transformation->getFilter();
552 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
553 :
554 1 : targetS = source + filter;
555 1 : sourceVault.setString(targetS);
556 1 : break;
557 1 : }
558 1 : case Transformation::FilterType::Prepend:
559 : {
560 1 : std::string filter = transformation->getFilter();
561 1 : replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
562 :
563 1 : targetS = filter + source;
564 1 : sourceVault.setString(targetS);
565 1 : break;
566 1 : }
567 4 : case Transformation::FilterType::Sum:
568 : {
569 4 : switch (transformation->getFilterNumberType()) {
570 1 : case 0: /* integer */
571 : {
572 1 : targetI = sourceVault.getInteger(success);
573 1 : if (success) targetI += transformation->getFilterI();
574 : //else return false; // should not happen (protected by schema)
575 1 : sourceVault.setInteger(targetI);
576 1 : break;
577 : }
578 2 : case 1: /* unsigned */
579 : {
580 2 : targetU = sourceVault.getUnsigned(success);
581 2 : if (success) targetU += transformation->getFilterU();
582 : //else return false; // should not happen (protected by schema)
583 2 : sourceVault.setUnsigned(targetU);
584 2 : break;
585 : }
586 1 : case 2: /* double */
587 : {
588 1 : targetF = sourceVault.getFloat(success);
589 1 : if (success) targetF += transformation->getFilterF();
590 : //else return false; // should not happen (protected by schema)
591 1 : sourceVault.setFloat(targetF);
592 1 : break;
593 : }
594 : }
595 4 : break;
596 : }
597 3 : case Transformation::FilterType::Multiply:
598 : {
599 3 : switch (transformation->getFilterNumberType()) {
600 1 : case 0: /* integer */
601 : {
602 1 : targetI = sourceVault.getInteger(success);
603 1 : if (success) targetI *= transformation->getFilterI();
604 : //else return false; // should not happen (protected by schema)
605 1 : sourceVault.setInteger(targetI);
606 1 : break;
607 : }
608 1 : case 1: /* unsigned */
609 : {
610 1 : targetU = sourceVault.getUnsigned(success);
611 1 : if (success) targetU *= transformation->getFilterU();
612 : //else return false; // should not happen (protected by schema)
613 1 : sourceVault.setUnsigned(targetU);
614 1 : break;
615 : }
616 1 : case 2: /* double */
617 : {
618 1 : targetF = sourceVault.getFloat(success);
619 1 : if (success) targetF *= transformation->getFilterF();
620 : //else return false; // should not happen (protected by schema)
621 1 : sourceVault.setFloat(targetF);
622 1 : break;
623 : }
624 : }
625 3 : break;
626 : }
627 6 : case Transformation::FilterType::ConditionVar: // TODO: if condition is false, source storage could be omitted to improve performance
628 : {
629 : // Get variable value for the variable name 'transformation->getFilter()':
630 6 : std::string varname = transformation->getFilter();
631 6 : bool reverse = (transformation->getFilter()[0] == '!');
632 6 : if (reverse) {
633 2 : varname.erase(0,1);
634 : }
635 6 : auto iter = variables.find(varname);
636 6 : bool varFound = (iter != variables.end());
637 6 : std::string varvalue{};
638 6 : if (varFound) {
639 1 : varvalue = iter->second;
640 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (local)", varname.c_str()), ERT_FILE_LOCATION));
641 : }
642 : else {
643 5 : nlohmann::json gvarvalue{};
644 5 : varFound = vault_->tryGet(varname, gvarvalue);
645 5 : if (varFound) varvalue = jsonToString(gvarvalue);
646 5 : LOGDEBUG(if (varFound) ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (vault)", varname.c_str()), ERT_FILE_LOCATION));
647 5 : }
648 :
649 6 : bool conditionVar = (varFound && !(varvalue.empty()));
650 6 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable value: '%s'", (varFound ? varvalue.c_str():"<undefined>")), ERT_FILE_LOCATION));
651 :
652 6 : if ((reverse && !conditionVar)||(!reverse && conditionVar)) {
653 4 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is true", (reverse ? "!":"")), ERT_FILE_LOCATION));
654 4 : sourceVault.setString(source);
655 : }
656 : else {
657 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is false", (reverse ? "!":"")), ERT_FILE_LOCATION));
658 2 : return false;
659 : }
660 4 : break;
661 12 : }
662 5 : case Transformation::FilterType::EqualTo:
663 : {
664 5 : std::string filter = transformation->getFilter();
665 5 : replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
666 :
667 : // Get value for the comparison 'transformation->getFilter()':
668 5 : if (source == filter) {
669 1 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is true", ERT_FILE_LOCATION));
670 1 : sourceVault.setString(source);
671 : }
672 : else {
673 4 : LOGDEBUG(ert::tracing::Logger::debug("EqualTo is false", ERT_FILE_LOCATION));
674 4 : return false;
675 : }
676 1 : break;
677 5 : }
678 2 : case Transformation::FilterType::DifferentFrom:
679 : {
680 2 : std::string filter = transformation->getFilter();
681 2 : replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
682 :
683 : // Get value for the comparison 'transformation->getFilter()':
684 2 : if (source != filter) {
685 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is true", ERT_FILE_LOCATION));
686 1 : sourceVault.setString(source);
687 : }
688 : else {
689 1 : LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is false", ERT_FILE_LOCATION));
690 1 : return false;
691 : }
692 1 : break;
693 2 : }
694 3 : case Transformation::FilterType::JsonConstraint:
695 : {
696 3 : nlohmann::json sobj = sourceVault.getObject(success);
697 : // should not happen (protected by schema)
698 : //if (!success) {
699 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for JsonConstraint filter must be a valid json object", ERT_FILE_LOCATION));
700 : // return false;
701 : //}
702 3 : std::string failReport;
703 3 : if (h2agent::model::jsonConstraint(sobj, transformation->getFilterObject(), failReport)) {
704 2 : sourceVault.setString("1");
705 : }
706 : else {
707 2 : sourceVault.setString(failReport);
708 : }
709 3 : break;
710 3 : }
711 2 : case Transformation::FilterType::SchemaId:
712 : {
713 2 : nlohmann::json sobj = sourceVault.getObject(success);
714 : // should not happen (protected by schema)
715 : //if (!success) {
716 : // LOGDEBUG(ert::tracing::Logger::debug("Source provided for SchemaId filter must be a valid json object", ERT_FILE_LOCATION));
717 : // return false;
718 : //}
719 2 : std::string failReport;
720 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)
721 2 : if (schema) {
722 2 : if (schema->validate(sobj, failReport)) {
723 2 : sourceVault.setString("1");
724 : }
725 : else {
726 1 : sourceVault.setString(failReport);
727 : }
728 : }
729 : else {
730 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);
731 : }
732 2 : break;
733 2 : }
734 5 : case Transformation::FilterType::Split:
735 : {
736 5 : std::int64_t size = transformation->getFilterI();
737 5 : std::uint64_t count = transformation->getFilterU();
738 5 : const std::string &sep = transformation->getFilter();
739 5 : const std::string &filler = transformation->getFilterFiller();
740 5 : bool numeric = (transformation->getFilterNumberType() != 0);
741 :
742 5 : std::uint64_t totalLen = static_cast<std::uint64_t>(size) * count;
743 5 : std::string padded = source;
744 :
745 : // Truncate (left) or pad (left) to reach totalLen
746 5 : if (padded.size() > totalLen) {
747 1 : padded = padded.substr(padded.size() - totalLen);
748 : }
749 4 : else if (padded.size() < totalLen && !filler.empty()) {
750 1 : std::string padding;
751 6 : while (padding.size() + padded.size() < totalLen) {
752 5 : padding += filler;
753 : }
754 1 : if (padding.size() + padded.size() > totalLen) {
755 0 : padding = padding.substr(padding.size() + padded.size() - totalLen);
756 : }
757 1 : padded = padding + padded;
758 1 : }
759 :
760 : // Split into groups and join
761 5 : targetS.clear();
762 24 : for (std::uint64_t i = 0; i < count; i++) {
763 19 : if (i > 0) targetS += sep;
764 19 : std::string group = padded.substr(i * size, size);
765 19 : if (numeric) {
766 : // Strip leading zeros by converting to unsigned long
767 : try {
768 12 : targetS += std::to_string(std::stoull(group));
769 : }
770 0 : catch (...) {
771 0 : targetS += group;
772 0 : }
773 : }
774 : else {
775 7 : targetS += group;
776 : }
777 19 : }
778 5 : sourceVault.setString(targetS);
779 5 : break;
780 5 : }
781 6 : case Transformation::FilterType::BaseConvert:
782 : {
783 6 : int baseIn = static_cast<int>(transformation->getFilterI());
784 6 : int baseOut = static_cast<int>(transformation->getFilterU());
785 6 : bool capital = (transformation->getFilterNumberType() != 0);
786 :
787 : try {
788 6 : unsigned long long val = std::stoull(source, nullptr, baseIn);
789 5 : if (baseOut == 10) {
790 1 : targetS = std::to_string(val);
791 : }
792 : else {
793 4 : targetS.clear();
794 4 : if (val == 0) { targetS = "0"; }
795 : else {
796 11 : while (val > 0) {
797 8 : int d = val % baseOut;
798 8 : targetS += (d < 10) ? char('0' + d) : char((capital ? 'A' : 'a') + d - 10);
799 8 : val /= baseOut;
800 : }
801 3 : std::reverse(targetS.begin(), targetS.end());
802 : }
803 : }
804 : }
805 1 : catch (...) {
806 1 : targetS = source;
807 1 : }
808 6 : sourceVault.setString(targetS);
809 6 : break;
810 : }
811 4 : case Transformation::FilterType::FStrptime:
812 : {
813 : // Parse date string → epoch number
814 4 : struct tm tm{};
815 4 : if (strptime(source.c_str(), transformation->getFilter().c_str(), &tm) == nullptr) {
816 1 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Strptime filter failed to parse '%s' with format '%s'", source.c_str(), transformation->getFilter().c_str()), ERT_FILE_LOCATION);
817 1 : return false;
818 : }
819 3 : std::int64_t epoch = static_cast<std::int64_t>(timegm(&tm));
820 3 : switch (transformation->getFilterNumberType()) {
821 1 : case 1: epoch *= 1000; break; // ms
822 0 : case 2: epoch *= 1000000; break; // us
823 0 : case 3: epoch *= 1000000000; break; // ns
824 : }
825 3 : sourceVault.setInteger(epoch);
826 3 : break;
827 : }
828 4 : case Transformation::FilterType::FStrftime:
829 : {
830 : // Format epoch number → date string
831 4 : std::int64_t epoch = sourceVault.getInteger(success);
832 4 : if (!success) {
833 1 : ert::tracing::Logger::error("Strftime filter requires a numeric source (epoch)", ERT_FILE_LOCATION);
834 1 : return false;
835 : }
836 3 : switch (transformation->getFilterNumberType()) {
837 1 : case 1: epoch /= 1000; break; // ms
838 0 : case 2: epoch /= 1000000; break; // us
839 0 : case 3: epoch /= 1000000000; break; // ns
840 : }
841 3 : time_t t = static_cast<time_t>(epoch);
842 3 : struct tm tm{};
843 3 : gmtime_r(&t, &tm);
844 : char buf[256];
845 3 : if (std::strftime(buf, sizeof(buf), transformation->getFilter().c_str(), &tm) == 0) {
846 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Strftime filter failed to format epoch %ld with format '%s'", (long)t, transformation->getFilter().c_str()), ERT_FILE_LOCATION);
847 0 : return false;
848 : }
849 3 : sourceVault.setString(std::string(buf));
850 3 : break;
851 : }
852 7 : case Transformation::FilterType::RegexKey:
853 : {
854 7 : bool objSuccess = false;
855 7 : nlohmann::json obj = sourceVault.getObject(objSuccess); // copy: setObject below clears sourceVault
856 7 : if (!objSuccess || !obj.is_object()) {
857 1 : ert::tracing::Logger::error("RegexKey filter requires a JSON object source", ERT_FILE_LOCATION);
858 1 : return false;
859 : }
860 12 : for (auto it = obj.begin(); it != obj.end(); ++it) {
861 11 : source = it.key(); // copy key to source (lives in transform() scope for matches lifetime)
862 11 : if (std::regex_match(source, matches, transformation->getFilterRegex())) {
863 10 : if (!sourceVault.setObject(it.value(), ""))
864 5 : return false;
865 5 : LOGDEBUG(
866 : std::stringstream ss;
867 : ss << "RegexKey filter: matched key '" << source << "'";
868 : for(size_t i=1; i < matches.size(); i++) {
869 : ss << " | group[" << i << "] = " << matches.str(i);
870 : }
871 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
872 : );
873 5 : return true;
874 : }
875 : }
876 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("RegexKey filter: no key matched '%s'", transformation->getFilter().c_str()), ERT_FILE_LOCATION));
877 1 : return false;
878 7 : }
879 3 : case Transformation::FilterType::Size:
880 : {
881 3 : bool objSuccess = false;
882 3 : nlohmann::json obj = sourceVault.getObject(objSuccess);
883 3 : if (objSuccess && (obj.is_object() || obj.is_array())) {
884 2 : targetS = std::to_string(obj.size());
885 : }
886 : else {
887 1 : source = sourceVault.getString(objSuccess);
888 1 : targetS = objSuccess ? std::to_string(source.size()) : "0";
889 : }
890 3 : sourceVault.setString(targetS);
891 3 : break;
892 3 : }
893 : }
894 : //}
895 : //catch (std::exception& e)
896 : //{
897 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
898 : //}
899 :
900 :
901 43 : return true;
902 60 : }
903 :
904 181 : bool AdminServerProvision::processTargets(std::shared_ptr<Transformation> transformation,
905 : TypeConverter &sourceVault,
906 : std::map<std::string, std::string>& variables,
907 : const std::smatch &matches,
908 : bool eraser,
909 : bool hasFilter,
910 : unsigned int &responseStatusCode,
911 : nlohmann::json &responseBodyJson,
912 : std::string &responseBodyAsString,
913 : nghttp2::asio_http2::header_map &responseHeaders,
914 : unsigned int &responseDelayMs,
915 : std::string &outState,
916 : std::string &outStateMethod,
917 : std::string &outStateUri,
918 : std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
919 : bool &breakCondition) const
920 : {
921 181 : bool success = false;
922 181 : std::string targetS;
923 181 : std::int64_t targetI = 0;
924 181 : std::uint64_t targetU = 0;
925 181 : double targetF = 0;
926 181 : bool boolean = false;
927 181 : nlohmann::json obj;
928 :
929 :
930 : try { // nlohmann::json exceptions
931 :
932 181 : std::string target = transformation->getTarget();
933 181 : std::string target2 = transformation->getTarget2(); // foreign outState URI
934 :
935 181 : replaceVariables(target, transformation->getTargetPatterns(), variables, vault_);
936 181 : if (!target2.empty()) {
937 8 : replaceVariables(target2, transformation->getTarget2Patterns(), variables, vault_);
938 : }
939 :
940 181 : switch (transformation->getTargetType()) {
941 65 : case Transformation::TargetType::ResponseBodyString:
942 : {
943 : // extraction
944 65 : targetS = sourceVault.getString(success);
945 65 : if (!success) return false;
946 : // assignment
947 65 : responseBodyAsString = targetS;
948 65 : break;
949 : }
950 1 : case Transformation::TargetType::ResponseBodyHexString:
951 : {
952 : // extraction
953 1 : targetS = sourceVault.getString(success);
954 1 : if (!success) return false;
955 : // assignment
956 1 : if (!h2agent::model::fromHexString(targetS, responseBodyAsString)) return false;
957 1 : break;
958 : }
959 27 : case Transformation::TargetType::ResponseBodyJson_String:
960 : {
961 : // extraction
962 27 : targetS = sourceVault.getString(success);
963 27 : if (!success) return false;
964 : // assignment
965 27 : nlohmann::json::json_pointer j_ptr(target);
966 26 : responseBodyJson[j_ptr] = targetS;
967 26 : break;
968 26 : }
969 6 : case Transformation::TargetType::ResponseBodyJson_Integer:
970 : {
971 : // extraction
972 6 : targetI = sourceVault.getInteger(success);
973 6 : if (!success) return false;
974 : // assignment
975 6 : nlohmann::json::json_pointer j_ptr(target);
976 6 : responseBodyJson[j_ptr] = targetI;
977 6 : break;
978 6 : }
979 5 : case Transformation::TargetType::ResponseBodyJson_Unsigned:
980 : {
981 : // extraction
982 5 : targetU = sourceVault.getUnsigned(success);
983 5 : if (!success) return false;
984 : // assignment
985 5 : nlohmann::json::json_pointer j_ptr(target);
986 5 : responseBodyJson[j_ptr] = targetU;
987 5 : break;
988 5 : }
989 2 : case Transformation::TargetType::ResponseBodyJson_Float:
990 : {
991 : // extraction
992 2 : targetF = sourceVault.getFloat(success);
993 2 : if (!success) return false;
994 : // assignment
995 2 : nlohmann::json::json_pointer j_ptr(target);
996 2 : responseBodyJson[j_ptr] = targetF;
997 2 : break;
998 2 : }
999 2 : case Transformation::TargetType::ResponseBodyJson_Boolean:
1000 : {
1001 : // extraction
1002 2 : boolean = sourceVault.getBoolean(success);
1003 2 : if (!success) return false;
1004 : // assignment
1005 2 : nlohmann::json::json_pointer j_ptr(target);
1006 2 : responseBodyJson[j_ptr] = boolean;
1007 2 : break;
1008 2 : }
1009 18 : case Transformation::TargetType::ResponseBodyJson_Object:
1010 : {
1011 :
1012 18 : if (eraser) {
1013 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into json path '%s'", target.c_str()), ERT_FILE_LOCATION));
1014 2 : if (target.empty()) {
1015 1 : responseBodyJson.erase(responseBodyJson.begin(), responseBodyJson.end());
1016 1 : return false;
1017 : }
1018 :
1019 : //erase() DOES NOT SUPPORT JSON POINTERS:
1020 : //nlohmann::json::json_pointer j_ptr(target);
1021 : //responseBodyJson.erase(j_ptr);
1022 : //
1023 : // For a path '/a/b/c' we must access to /a/b and then erase "c":
1024 1 : size_t lastSlashPos = target.find_last_of("/");
1025 : // lastSlashPos will never be std::string::npos here
1026 1 : std::string parentPath = target.substr(0, lastSlashPos);
1027 1 : std::string childKey = "";
1028 1 : if (lastSlashPos + 1 < target.size()) childKey = target.substr(lastSlashPos + 1, target.size());
1029 1 : nlohmann::json::json_pointer j_ptr(parentPath);
1030 1 : responseBodyJson[j_ptr].erase(childKey);
1031 1 : return false;
1032 1 : }
1033 :
1034 : // extraction will be object if possible, falling back to the rest of formats with this priority: string, integer, unsigned, float, boolean
1035 : // assignment for valid extraction
1036 16 : nlohmann::json::json_pointer j_ptr(target);
1037 :
1038 : // Native types for SOURCES:
1039 : //
1040 : // [string] request.uri, request.uri.path, request.header, randomset, strftime, var, vault, value, txtFile, binFile, command
1041 : // [object] request.body, response.body, serverEvent (when target is also object, it could be promoted to string, unsigned, integer, float or boolean).
1042 : // [integer] random, timestamp
1043 : // [unsigned] recvseq
1044 : // [float] math.*
1045 : // [boolean] NONE
1046 : // So, depending on the target, the corresponding getter (getInteger, getString, etc.) will be used: WE DO NOT WANT FORCE CONVERSIONS:
1047 : // Note that there is not sources with boolean as native type, so boolean getter is never reached (so commented to avoid UT coverage fault).
1048 : //
1049 16 : switch (sourceVault.getNativeType()) {
1050 6 : case TypeConverter::NativeType::Object:
1051 6 : obj = sourceVault.getObject(success);
1052 6 : if (success) {
1053 6 : if (target.empty()) {
1054 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
1055 : }
1056 : else {
1057 5 : responseBodyJson[j_ptr] = obj;
1058 : }
1059 : }
1060 6 : break;
1061 :
1062 2 : case TypeConverter::NativeType::String:
1063 2 : targetS = sourceVault.getString(success);
1064 2 : if (success) responseBodyJson[j_ptr] = targetS;
1065 2 : break;
1066 :
1067 5 : case TypeConverter::NativeType::Integer:
1068 5 : targetI = sourceVault.getInteger(success);
1069 5 : if (success) responseBodyJson[j_ptr] = targetI;
1070 5 : break;
1071 :
1072 1 : case TypeConverter::NativeType::Unsigned:
1073 1 : targetU = sourceVault.getUnsigned(success);
1074 1 : if (success) responseBodyJson[j_ptr] = targetU;
1075 1 : break;
1076 :
1077 1 : case TypeConverter::NativeType::Float:
1078 1 : targetF = sourceVault.getFloat(success);
1079 1 : if (success) responseBodyJson[j_ptr] = targetF;
1080 1 : break;
1081 :
1082 : // Not reached at the moment:
1083 1 : case TypeConverter::NativeType::Boolean:
1084 1 : boolean = sourceVault.getBoolean(success);
1085 1 : if (success) responseBodyJson[j_ptr] = boolean;
1086 1 : break;
1087 : }
1088 16 : break;
1089 16 : }
1090 3 : case Transformation::TargetType::ResponseBodyJson_JsonString:
1091 : {
1092 :
1093 : // assignment for valid extraction
1094 3 : nlohmann::json::json_pointer j_ptr(target);
1095 :
1096 : // extraction
1097 3 : targetS = sourceVault.getString(success);
1098 3 : if (!success) return false;
1099 3 : if (!h2agent::model::parseJsonContent(targetS, obj))
1100 1 : return false;
1101 :
1102 : // assignment
1103 2 : if (target.empty()) {
1104 1 : responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
1105 : }
1106 : else {
1107 1 : responseBodyJson[j_ptr] = obj;
1108 : }
1109 2 : break;
1110 3 : }
1111 1 : case Transformation::TargetType::ResponseHeader_t:
1112 : {
1113 : // extraction
1114 1 : targetS = sourceVault.getString(success);
1115 1 : if (!success) return false;
1116 : // assignment
1117 1 : responseHeaders.emplace(target, nghttp2::asio_http2::header_value{targetS});
1118 1 : break;
1119 : }
1120 5 : case Transformation::TargetType::ResponseStatusCode_t:
1121 : {
1122 : // extraction
1123 5 : targetU = sourceVault.getUnsigned(success);
1124 5 : if (!success) return false;
1125 : // assignment
1126 5 : responseStatusCode = targetU;
1127 5 : break;
1128 : }
1129 1 : case Transformation::TargetType::ResponseDelayMs:
1130 : {
1131 : // extraction
1132 1 : targetU = sourceVault.getUnsigned(success);
1133 1 : if (!success) return false;
1134 : // assignment
1135 1 : responseDelayMs = targetU;
1136 1 : break;
1137 : }
1138 13 : case Transformation::TargetType::TVar:
1139 : {
1140 13 : if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
1141 1 : std::string varname;
1142 1 : if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
1143 1 : variables[target] = matches.str(0); // variable "as is" stores the entire match (backward compatible)
1144 4 : for(size_t i=1; i < matches.size(); i++) {
1145 3 : varname = target;
1146 3 : varname += ".";
1147 3 : varname += std::to_string(i);
1148 3 : variables[varname] = matches.str(i);
1149 3 : LOGDEBUG(
1150 : std::stringstream ss;
1151 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
1152 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
1153 : );
1154 : }
1155 : }
1156 1 : }
1157 12 : else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexKey) {
1158 : // Store the value in the target variable
1159 1 : targetS = sourceVault.getString(success);
1160 1 : if (!success) return false;
1161 1 : variables[target] = targetS;
1162 : // Store matched key (.0) and capture groups (.1, .2, ...) in variables
1163 4 : for(size_t i=0; i < matches.size(); i++) {
1164 3 : std::string varname = target + "." + std::to_string(i);
1165 3 : variables[varname] = matches.str(i);
1166 3 : LOGDEBUG(
1167 : std::stringstream ss;
1168 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
1169 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
1170 : );
1171 3 : }
1172 : }
1173 : else {
1174 : // extraction
1175 11 : targetS = sourceVault.getString(success);
1176 11 : if (!success) return false;
1177 :
1178 11 : if (hasFilter) {
1179 6 : if(transformation->getFilterType() == Transformation::FilterType::JsonConstraint) {
1180 1 : if (targetS != "1") { // this is a fail report
1181 1 : variables[target + ".fail"] = targetS;
1182 1 : targetS = "";
1183 : }
1184 : }
1185 5 : else if (transformation->getFilterType() == Transformation::FilterType::SchemaId) {
1186 0 : if (targetS != "1") { // this is a fail report
1187 0 : variables[target + ".fail"] = targetS;
1188 0 : targetS = "";
1189 : }
1190 : }
1191 : }
1192 :
1193 : // assignment
1194 11 : variables[target] = targetS;
1195 : }
1196 13 : break;
1197 : }
1198 10 : case Transformation::TargetType::TGVar:
1199 : {
1200 10 : std::string gvarPath = transformation->getTarget2();
1201 10 : if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
1202 :
1203 10 : if (eraser) {
1204 : bool exists;
1205 1 : vault_->remove(target, exists);
1206 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into vault entry '%s' (%s)", target.c_str(), exists ? "removed":"missing"), ERT_FILE_LOCATION));
1207 : }
1208 9 : else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
1209 1 : if (matches.size() >=1) {
1210 : // Store regex captures as a JSON object: {"0": "full", "1": "group1", ...}
1211 1 : nlohmann::json captureObj = nlohmann::json::object();
1212 1 : captureObj["0"] = matches.str(0);
1213 4 : for(size_t i=1; i < matches.size(); i++) {
1214 3 : captureObj[std::to_string(i)] = matches.str(i);
1215 : }
1216 1 : if (gvarPath.empty()) {
1217 1 : vault_->load(target, std::move(captureObj));
1218 : } else {
1219 0 : vault_->loadAtPath(target, gvarPath, captureObj);
1220 : }
1221 1 : }
1222 : }
1223 8 : else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexKey) {
1224 : // Store value in vault (as today)
1225 3 : bool objSuccess = false;
1226 3 : const nlohmann::json &obj = sourceVault.getObject(objSuccess);
1227 3 : if (objSuccess) {
1228 2 : if (gvarPath.empty()) {
1229 2 : vault_->load(target, obj);
1230 : } else {
1231 0 : vault_->loadAtPath(target, gvarPath, obj);
1232 : }
1233 : } else {
1234 1 : targetS = sourceVault.getString(success);
1235 1 : if (!success) return false;
1236 1 : nlohmann::json val(targetS);
1237 1 : if (gvarPath.empty()) {
1238 1 : vault_->load(target, std::move(val));
1239 : } else {
1240 0 : vault_->loadAtPath(target, gvarPath, val);
1241 : }
1242 1 : }
1243 : // Store matched key (.0) and capture groups (.1, .2, ...) in variables
1244 8 : for(size_t i=0; i < matches.size(); i++) {
1245 5 : std::string varname = target + "." + std::to_string(i);
1246 5 : variables[varname] = matches.str(i);
1247 5 : LOGDEBUG(
1248 : std::stringstream ss;
1249 : ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
1250 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
1251 : );
1252 5 : }
1253 : }
1254 : else {
1255 : // Try to extract as json object first, fall back to string
1256 5 : bool objSuccess = false;
1257 5 : const nlohmann::json &obj = sourceVault.getObject(objSuccess);
1258 5 : if (objSuccess) {
1259 1 : if (gvarPath.empty()) {
1260 1 : vault_->load(target, obj);
1261 : } else {
1262 0 : vault_->loadAtPath(target, gvarPath, obj);
1263 : }
1264 : } else {
1265 4 : targetS = sourceVault.getString(success);
1266 4 : if (!success) return false;
1267 4 : nlohmann::json val(targetS);
1268 4 : if (gvarPath.empty()) {
1269 2 : vault_->load(target, std::move(val));
1270 : } else {
1271 2 : vault_->loadAtPath(target, gvarPath, val);
1272 : }
1273 4 : }
1274 : }
1275 10 : break;
1276 10 : }
1277 3 : case Transformation::TargetType::TGVarJson_Object:
1278 : {
1279 3 : std::string gvarPath = transformation->getTarget2();
1280 3 : if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
1281 :
1282 3 : bool objSuccess = false;
1283 3 : const nlohmann::json &obj = sourceVault.getObject(objSuccess);
1284 3 : if (!objSuccess) return false;
1285 :
1286 2 : if (gvarPath.empty()) {
1287 1 : vault_->load(target, obj);
1288 : } else {
1289 1 : vault_->loadAtPath(target, gvarPath, obj);
1290 : }
1291 2 : break;
1292 3 : }
1293 4 : case Transformation::TargetType::TGVarJson_JsonString:
1294 : {
1295 4 : std::string gvarPath = transformation->getTarget2();
1296 4 : if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
1297 :
1298 4 : targetS = sourceVault.getString(success);
1299 4 : if (!success) return false;
1300 4 : if (!h2agent::model::parseJsonContent(targetS, obj))
1301 1 : return false;
1302 :
1303 3 : if (gvarPath.empty()) {
1304 2 : vault_->load(target, obj);
1305 : } else {
1306 1 : vault_->loadAtPath(target, gvarPath, obj);
1307 : }
1308 3 : break;
1309 4 : }
1310 2 : case Transformation::TargetType::OutState:
1311 : {
1312 : // extraction
1313 2 : targetS = sourceVault.getString(success);
1314 2 : if (!success) return false;
1315 : // assignments
1316 2 : outState = targetS;
1317 2 : outStateMethod = target; // empty on regular usage
1318 2 : outStateUri = target2; // empty on regular usage
1319 2 : break;
1320 : }
1321 2 : case Transformation::TargetType::TTxtFile:
1322 : {
1323 : // extraction
1324 2 : targetS = sourceVault.getString(success);
1325 2 : if (!success) return false;
1326 :
1327 2 : if (eraser) {
1328 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into text file '%s'", target.c_str()), ERT_FILE_LOCATION));
1329 1 : file_manager_->empty(target/*path*/);
1330 : }
1331 : else {
1332 : // assignments
1333 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
1334 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
1335 : // files type.
1336 1 : file_manager_->write(target/*path*/, targetS/*data*/, true/*text*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
1337 : }
1338 2 : break;
1339 : }
1340 2 : case Transformation::TargetType::TBinFile:
1341 : {
1342 : // extraction
1343 2 : targetS = sourceVault.getString(success);
1344 2 : if (!success) return false;
1345 :
1346 2 : if (eraser) {
1347 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into binary file '%s'", target.c_str()), ERT_FILE_LOCATION));
1348 1 : file_manager_->empty(target/*path*/);
1349 : }
1350 : else {
1351 : // assignments
1352 1 : bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
1353 : // even if @{varname} is missing (empty value) we consider the intention to allow force short term
1354 : // files type.
1355 1 : file_manager_->write(target/*path*/, targetS/*data*/, false/*binary*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
1356 : }
1357 2 : break;
1358 : }
1359 1 : case Transformation::TargetType::UDPSocket:
1360 : {
1361 : // extraction
1362 1 : targetS = sourceVault.getString(success);
1363 1 : if (!success) return false;
1364 :
1365 : // assignments
1366 : // Possible delay provided in 'target': <path>|<delay>
1367 1 : std::string path = target;
1368 1 : size_t lastDotPos = target.find_last_of("|");
1369 1 : unsigned int delayMs = atoi(target.substr(lastDotPos + 1).c_str());
1370 1 : path = target.substr(0, lastDotPos);
1371 :
1372 1 : LOGDEBUG(
1373 : std::string msg = ert::tracing::Logger::asString("UDPSocket '%s' target, delayed %u milliseconds, in transformation item", path.c_str(), delayMs);
1374 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1375 : );
1376 :
1377 1 : socket_manager_->write(path, targetS/*data*/, delayMs * 1000 /* usecs */);
1378 1 : break;
1379 1 : }
1380 3 : case Transformation::TargetType::ServerEventToPurge:
1381 : {
1382 3 : if (!eraser) {
1383 1 : LOGDEBUG(ert::tracing::Logger::debug("'ServerEventToPurge' target type only works with 'eraser' source type. This transformation will be ignored.", ERT_FILE_LOCATION));
1384 2 : return false;
1385 : }
1386 : // transformation->getTargetTokenized() is a vector:
1387 : //
1388 : // requestMethod: index 0
1389 : // requestUri: index 1
1390 : // eventNumber: index 2
1391 : // recvseq: index 3
1392 2 : std::string event_method = transformation->getTargetTokenized()[0];
1393 2 : replaceVariables(event_method, transformation->getTargetPatterns(), variables, vault_);
1394 2 : std::string event_uri = transformation->getTargetTokenized()[1];
1395 2 : replaceVariables(event_uri, transformation->getTargetPatterns(), variables, vault_);
1396 2 : std::string event_number = transformation->getTargetTokenized()[2];
1397 2 : replaceVariables(event_number, transformation->getTargetPatterns(), variables, vault_);
1398 2 : std::string event_recvseq = transformation->getTargetTokenized()[3];
1399 2 : replaceVariables(event_recvseq, transformation->getTargetPatterns(), variables, vault_);
1400 :
1401 2 : bool serverDataDeleted = false;
1402 :
1403 2 : if (!event_recvseq.empty()) {
1404 : // Stable addressing by receive sequence:
1405 0 : DataKey dkey(event_method, event_uri);
1406 0 : serverDataDeleted = mock_server_events_data_->removeEventByRecvSeq(dkey, (std::uint64_t)std::stoull(event_recvseq));
1407 0 : }
1408 : else {
1409 : // Positional addressing by event number:
1410 2 : EventKey ekey(event_method, event_uri, event_number);
1411 2 : bool success = mock_server_events_data_->clear(serverDataDeleted, ekey);
1412 :
1413 2 : if (!success) {
1414 1 : LOGDEBUG(
1415 : std::string msg = ert::tracing::Logger::asString("Unexpected error while removing server data event '%s' in transformation item", transformation->getTarget().c_str());
1416 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1417 : );
1418 1 : return false;
1419 : }
1420 2 : }
1421 :
1422 1 : LOGDEBUG(
1423 : std::string msg = ert::tracing::Logger::asString("Server event '%s' removal result: %s", transformation->getTarget().c_str(), (serverDataDeleted ? "SUCCESS":"NOTHING REMOVED"));
1424 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1425 : );
1426 1 : break;
1427 8 : }
1428 3 : case Transformation::TargetType::ClientProvision_t:
1429 : {
1430 3 : std::string clientProvisionId = transformation->getTarget();
1431 3 : replaceVariables(clientProvisionId, transformation->getTargetPatterns(), variables, vault_);
1432 :
1433 : // Source acts as conditional gate: non-empty = trigger, empty/eraser = skip
1434 4 : targetS = eraser ? "" : sourceVault.getString(success);
1435 3 : if (targetS.empty()) {
1436 1 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString(
1437 : "Client provision trigger skipped (empty source condition): id='%s'", clientProvisionId.c_str()), ERT_FILE_LOCATION));
1438 1 : break;
1439 : }
1440 :
1441 2 : std::string inState = transformation->getTarget2();
1442 2 : replaceVariables(inState, transformation->getTarget2Patterns(), variables, vault_);
1443 2 : clientProvisionTriggers.emplace_back(clientProvisionId, inState);
1444 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString(
1445 : "Scheduled client provision trigger: id='%s', inState='%s'", clientProvisionId.c_str(), inState.c_str()), ERT_FILE_LOCATION));
1446 2 : break;
1447 3 : }
1448 2 : case Transformation::TargetType::Break:
1449 : {
1450 : // extraction
1451 2 : targetS = sourceVault.getString(success);
1452 2 : if (!success) return false;
1453 : // assignments
1454 2 : if (targetS.empty()) {
1455 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action ignored (empty string as source provided)", ERT_FILE_LOCATION));
1456 1 : return false;
1457 : }
1458 :
1459 1 : breakCondition = true;
1460 1 : LOGDEBUG(ert::tracing::Logger::debug("Break action triggered: ignoring remaining transformation items", ERT_FILE_LOCATION));
1461 1 : return false;
1462 : break;
1463 : }
1464 : // this won't happen due to schema for server target types:
1465 0 : case Transformation::TargetType::RequestBodyString:
1466 : case Transformation::TargetType::RequestBodyHexString:
1467 : case Transformation::TargetType::RequestBodyJson_String:
1468 : case Transformation::TargetType::RequestBodyJson_Integer:
1469 : case Transformation::TargetType::RequestBodyJson_Unsigned:
1470 : case Transformation::TargetType::RequestBodyJson_Float:
1471 : case Transformation::TargetType::RequestBodyJson_Boolean:
1472 : case Transformation::TargetType::RequestBodyJson_Object:
1473 : case Transformation::TargetType::RequestBodyJson_JsonString:
1474 : case Transformation::TargetType::RequestHeader_t:
1475 : case Transformation::TargetType::RequestDelayMs:
1476 : case Transformation::TargetType::RequestTimeoutMs:
1477 : case Transformation::TargetType::ClientEventToPurge:
1478 : case Transformation::TargetType::RequestUri_t:
1479 : case Transformation::TargetType::RequestMethod_t:
1480 0 : break;
1481 : }
1482 191 : }
1483 1 : catch (std::exception& e)
1484 : {
1485 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1486 1 : }
1487 :
1488 :
1489 172 : return true;
1490 181 : }
1491 :
1492 12 : void AdminServerProvision::executeOnFilterFail(
1493 : const std::vector<std::shared_ptr<Transformation>> &fallbacks,
1494 : const std::string &requestUri, const std::string &requestUriPath,
1495 : const std::map<std::string, std::string> &requestQueryParametersMap,
1496 : const DataPart &requestBodyDataPart, const nghttp2::asio_http2::header_map &requestHeaders,
1497 : std::uint64_t generalUniqueServerSequence, TypeConverter &sourceVault,
1498 : std::map<std::string, std::string> &variables,
1499 : bool usesResponseBodyAsTransformationJsonTarget,
1500 : unsigned int &responseStatusCode, nlohmann::json &responseBodyJson, std::string &responseBody,
1501 : nghttp2::asio_http2::header_map &responseHeaders, unsigned int &responseDelayMs,
1502 : std::string &outState, std::string &outStateMethod, std::string &outStateUri,
1503 : std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers, bool &breakCondition) const {
1504 :
1505 16 : for (const auto &fallback : fallbacks) {
1506 4 : std::string fbSource{};
1507 4 : std::smatch fbMatches{};
1508 4 : bool fbEraser = false;
1509 4 : if (!processSources(fallback, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, fbEraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) continue;
1510 4 : if (fallback->hasFilter() && (fbEraser || !processFilters(fallback, sourceVault, variables, fbMatches, fbSource))) {
1511 1 : executeOnFilterFail(fallback->getOnFilterFail(), requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, generalUniqueServerSequence, sourceVault, variables, usesResponseBodyAsTransformationJsonTarget, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
1512 1 : continue;
1513 : }
1514 3 : processTargets(fallback, sourceVault, variables, fbMatches, fbEraser, fallback->hasFilter(), responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
1515 5 : }
1516 12 : }
1517 :
1518 153 : void AdminServerProvision::transform( const std::string &requestUri,
1519 : const std::string &requestUriPath,
1520 : const std::map<std::string, std::string> &requestQueryParametersMap,
1521 : DataPart &requestBodyDataPart,
1522 : const nghttp2::asio_http2::header_map &requestHeaders,
1523 : std::uint64_t generalUniqueServerSequence,
1524 :
1525 : /* OUTPUT PARAMETERS WHICH ALREADY HAVE DEFAULT VALUES BEFORE TRANSFORMATIONS: */
1526 : unsigned int &responseStatusCode,
1527 : nghttp2::asio_http2::header_map &responseHeaders,
1528 : std::string &responseBody,
1529 : unsigned int &responseDelayMs,
1530 : std::string &outState,
1531 : std::string &outStateMethod,
1532 : std::string &outStateUri,
1533 : std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
1534 : std::map<std::string, std::string> &variables
1535 : )
1536 : {
1537 : // Default values without transformations:
1538 153 : responseStatusCode = getResponseCode();
1539 153 : responseHeaders = getResponseHeaders();
1540 153 : responseDelayMs = getResponseDelayMilliseconds();
1541 153 : outState = getOutState(); // prepare next request state, with URI path before transformed with matching algorithms
1542 153 : outStateMethod = "";
1543 153 : outStateUri = "";
1544 :
1545 : // Check if the request body must be decoded:
1546 153 : bool mustDecodeRequestBody = false;
1547 153 : if (getRequestSchema()) {
1548 2 : mustDecodeRequestBody = true;
1549 : }
1550 : else {
1551 332 : for (const auto &t : transformations_) {
1552 203 : if (t->getSourceType() == Transformation::SourceType::RequestBody) {
1553 22 : if (!requestBodyDataPart.str().empty()) {
1554 21 : mustDecodeRequestBody = true;
1555 : }
1556 : else {
1557 1 : LOGINFORMATIONAL(ert::tracing::Logger::informational("Empty request body received: some transformations will be ignored", ERT_FILE_LOCATION));
1558 : }
1559 22 : break;
1560 : }
1561 : }
1562 : }
1563 153 : if (mustDecodeRequestBody) {
1564 23 : requestBodyDataPart.decode(requestHeaders);
1565 : }
1566 :
1567 : // Request schema validation (normally used to validate native json received, but can also be used to validate the agent json representation (multipart, text, etc.)):
1568 153 : if (getRequestSchema()) {
1569 2 : std::string error{};
1570 2 : if (!getRequestSchema()->validate(requestBodyDataPart.getJson(), error)) {
1571 1 : responseStatusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
1572 1 : return; // INTERRUPT TRANSFORMATIONS
1573 : }
1574 2 : }
1575 :
1576 : // Find out if response body will need to be cloned (this is true if any transformation uses it as target):
1577 152 : bool usesResponseBodyAsTransformationJsonTarget = false;
1578 289 : for (const auto &t : transformations_) {
1579 357 : if (t->getTargetType() == Transformation::TargetType::ResponseBodyJson_String ||
1580 332 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Integer ||
1581 321 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Unsigned ||
1582 316 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Float ||
1583 312 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Boolean ||
1584 512 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Object ||
1585 140 : t->getTargetType() == Transformation::TargetType::ResponseBodyJson_JsonString) {
1586 50 : usesResponseBodyAsTransformationJsonTarget = true;
1587 50 : break;
1588 : }
1589 : }
1590 :
1591 152 : nlohmann::json responseBodyJson;
1592 152 : if (usesResponseBodyAsTransformationJsonTarget) {
1593 50 : responseBodyJson = getResponseBody(); // clone provision response body to manipulate this copy and finally we will dump() it over 'responseBody':
1594 : // if(usesResponseBodyAsTransformationJsonTarget) responseBody = responseBodyJson.dump(); <--- place this after transformations (*)
1595 : }
1596 : else {
1597 102 : responseBody = getResponseBodyAsString(); // this could be overwritten by targets ResponseBodyString or ResponseBodyHexString
1598 : }
1599 :
1600 : // Type converter:
1601 152 : TypeConverter sourceVault{};
1602 :
1603 : // Apply transformations sequentially
1604 152 : bool breakCondition = false;
1605 360 : for (const auto &transformation : transformations_) {
1606 :
1607 209 : if (breakCondition) break;
1608 :
1609 208 : bool eraser = false;
1610 :
1611 208 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Processing transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1612 :
1613 : // SOURCES: RequestUri, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser, Math, Random, Timestamp, Strftime, Recvseq, SVar, SGvar, Value, ServerEvent, InState
1614 208 : if (!processSources(transformation, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, eraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) {
1615 19 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on source", ERT_FILE_LOCATION));
1616 39 : continue;
1617 : }
1618 :
1619 189 : std::smatch matches; // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
1620 : // So, we can't use 'matches' as container because source may change: BUT, using that source exclusively, it will work (*)
1621 189 : std::string source; // Now, this never will be out of scope, and 'matches' will be valid.
1622 :
1623 : // FILTERS: RegexCapture, RegexReplace, Append, Prepend, Sum, Multiply, ConditionVar, EqualTo, DifferentFrom, JsonConstraint, SchemaId
1624 189 : bool hasFilter = transformation->hasFilter();
1625 189 : if (hasFilter) {
1626 59 : if (eraser || !processFilters(transformation, sourceVault, variables, matches, source)) {
1627 11 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on filter", ERT_FILE_LOCATION));
1628 11 : if (eraser) LOGWARNING(ert::tracing::Logger::warning("Filter is not allowed when using 'eraser' source type. Transformation will be ignored.", ERT_FILE_LOCATION));
1629 :
1630 : // onFilterFail:
1631 11 : executeOnFilterFail(transformation->getOnFilterFail(), requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, generalUniqueServerSequence, sourceVault, variables, usesResponseBodyAsTransformationJsonTarget, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
1632 :
1633 11 : continue;
1634 : }
1635 : }
1636 :
1637 : // 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
1638 178 : if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition)) {
1639 9 : LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
1640 9 : continue;
1641 : }
1642 :
1643 209 : }
1644 :
1645 : // (*) Regenerate final responseBody after transformations:
1646 152 : if(usesResponseBodyAsTransformationJsonTarget && !responseBodyJson.empty()) {
1647 : try {
1648 49 : responseBody = responseBodyJson.dump(); // this may arise type error, for example in case of trying to set json field value with binary data:
1649 : // When having a provision transformation from 'request.body' to 'response.body.json.string./whatever':
1650 : // echo -en '\x80\x01' | curl --http2-prior-knowledge -i -H 'content-type:application/octet-stream' -X GET "<traffic url>/uri" --data-binary @-
1651 : //
1652 : // This is not valid and must be protected. The user should use another kind of target to store binary.
1653 : }
1654 1 : catch (const std::exception& e)
1655 : {
1656 1 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1657 1 : }
1658 : }
1659 :
1660 : // Response schema validation (not supported for response body created by non-json targets, to simplify the fact to parse need on ResponseBodyString/ResponseBodyHexString):
1661 152 : if (getResponseSchema()) {
1662 1 : std::string error{};
1663 1 : if (!getResponseSchema()->validate(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), error)) {
1664 1 : responseStatusCode = ert::http2comm::ResponseCode::INTERNAL_SERVER_ERROR; // 500: built response will be anyway sent although status code is overwritten with internal server error.
1665 : }
1666 1 : }
1667 152 : }
1668 :
1669 171 : bool AdminServerProvision::load(const nlohmann::json &j, bool regexMatchingConfigured) {
1670 :
1671 : // Store whole document (useful for GET operation)
1672 171 : json_ = j;
1673 :
1674 : // Mandatory
1675 171 : auto requestMethod_it = j.find("requestMethod");
1676 171 : request_method_ = *requestMethod_it;
1677 :
1678 171 : auto it = j.find("responseCode");
1679 171 : response_code_ = *it;
1680 :
1681 : // Optional
1682 171 : it = j.find("requestUri");
1683 171 : if (it != j.end() && it->is_string()) {
1684 171 : request_uri_ = *it;
1685 : }
1686 :
1687 171 : it = j.find("inState");
1688 171 : if (it != j.end() && it->is_string()) {
1689 3 : in_state_ = *it;
1690 3 : if (in_state_.empty()) in_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1691 : }
1692 :
1693 171 : it = j.find("outState");
1694 171 : if (it != j.end() && it->is_string()) {
1695 2 : out_state_ = *it;
1696 2 : if (out_state_.empty()) out_state_ = DEFAULT_ADMIN_PROVISION_STATE;
1697 : }
1698 :
1699 171 : it = j.find("requestSchemaId");
1700 171 : if (it != j.end() && it->is_string()) {
1701 7 : request_schema_id_ = *it;
1702 7 : if (request_schema_id_.empty()) {
1703 1 : ert::tracing::Logger::error("Invalid empty request schema identifier", ERT_FILE_LOCATION);
1704 1 : return false;
1705 : }
1706 : }
1707 :
1708 170 : it = j.find("responseSchemaId");
1709 170 : if (it != j.end() && it->is_string()) {
1710 7 : response_schema_id_ = *it;
1711 7 : if (response_schema_id_.empty()) {
1712 1 : ert::tracing::Logger::error("Invalid empty response schema identifier", ERT_FILE_LOCATION);
1713 1 : return false;
1714 : }
1715 : }
1716 :
1717 169 : it = j.find("responseHeaders");
1718 169 : if (it != j.end() && it->is_object()) {
1719 483 : for (auto& [key, val] : it->items())
1720 483 : response_headers_.emplace(key, nghttp2::asio_http2::header_value{val});
1721 : }
1722 :
1723 169 : it = j.find("responseBody");
1724 169 : if (it != j.end()) {
1725 161 : if (it->is_object() || it->is_array()) {
1726 155 : response_body_ = *it;
1727 155 : response_body_string_ = response_body_.dump(); // valid as cache for static responses (not updated with transformations)
1728 : }
1729 6 : else if (it->is_string()) {
1730 1 : response_body_string_ = *it;
1731 : }
1732 5 : else if (it->is_number_integer() || it->is_number_unsigned()) {
1733 : //response_body_integer_ = *it;
1734 2 : int number = *it;
1735 2 : response_body_string_ = std::to_string(number);
1736 : }
1737 3 : else if (it->is_number_float()) {
1738 : //response_body_number_ = *it;
1739 1 : response_body_string_ = std::to_string(double(*it));
1740 : }
1741 2 : else if (it->is_boolean()) {
1742 : //response_body_boolean_ = *it;
1743 1 : response_body_string_ = ((bool)(*it) ? "true":"false");
1744 : }
1745 1 : else if (it->is_null()) {
1746 : //response_body_null_ = true;
1747 1 : response_body_string_ = "null";
1748 : }
1749 : }
1750 :
1751 169 : it = j.find("responseDelayMs");
1752 169 : if (it != j.end() && it->is_number()) {
1753 156 : response_delay_ms_ = *it;
1754 : }
1755 :
1756 169 : auto transform_it = j.find("transform");
1757 169 : if (transform_it != j.end()) {
1758 368 : for (auto it : *transform_it) { // "it" is of type json::reference and has no key() member
1759 218 : loadTransformation(it);
1760 218 : }
1761 150 : if (!transformations_.empty() && transformations_.back()->getTargetType() == Transformation::TargetType::Break) {
1762 0 : LOGWARNING(ert::tracing::Logger::warning("Break as last 'transform' item is illogical (no further items to interrupt)", ERT_FILE_LOCATION));
1763 : }
1764 : }
1765 :
1766 : // Store key:
1767 169 : h2agent::model::calculateStringKey(key_, in_state_, request_method_, request_uri_);
1768 :
1769 169 : if (regexMatchingConfigured) {
1770 : // Precompile regex with key, only for 'RegexMatching' algorithm:
1771 : try {
1772 6 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Assigning regex: %s", key_.c_str()), ERT_FILE_LOCATION));
1773 6 : regex_.assign(key_, std::regex::optimize);
1774 : }
1775 2 : catch (std::regex_error &e) {
1776 4 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
1777 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);
1778 2 : return false;
1779 2 : }
1780 : }
1781 :
1782 167 : return true;
1783 : }
1784 :
1785 218 : void AdminServerProvision::loadTransformation(const nlohmann::json &j) {
1786 :
1787 218 : LOGDEBUG(
1788 : std::string msg = ert::tracing::Logger::asString("Loading transformation item: %s", j.dump().c_str()); // avoid newlines in traces (dump(n) pretty print)
1789 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
1790 : );
1791 :
1792 : // Transformation object to fill:
1793 218 : auto transformation = std::make_shared<Transformation>();
1794 :
1795 218 : if (transformation->load(j)) {
1796 215 : transformations_.push_back(transformation);
1797 215 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Loaded transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
1798 : }
1799 : else {
1800 6 : ert::tracing::Logger::error("Discarded transform item due to incoherent data", ERT_FILE_LOCATION);
1801 : }
1802 218 : }
1803 :
1804 :
1805 : }
1806 : }
1807 :
|