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