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 :
37 : #include <ert/tracing/Logger.hpp>
38 :
39 : #include <Transformation.hpp>
40 : #include <functions.hpp>
41 :
42 : #include <sstream>
43 : #include <algorithm>
44 :
45 : namespace h2agent
46 : {
47 : namespace model
48 : {
49 :
50 595 : void Transformation::collectVariablePatterns(const std::string &str, std::map<std::string, std::string> &patterns) {
51 :
52 595 : static std::regex re("@\\{[^\\{\\}]*\\}", std::regex::optimize); // @{[^{}]*} with curly braces escaped
53 : // or: R"(@\{[^\{\}]*\})"
54 :
55 595 : std::string::const_iterator it(str.cbegin());
56 595 : std::smatch matches;
57 595 : std::string pattern;
58 595 : patterns.clear();
59 608 : while (std::regex_search(it, str.cend(), matches, re)) {
60 13 : it = matches.suffix().first;
61 13 : pattern = matches[0];
62 13 : patterns[pattern] = pattern.substr(2, pattern.size()-3); // @{foo} -> foo
63 : }
64 595 : }
65 :
66 197 : bool Transformation::load(const nlohmann::json &j) {
67 :
68 197 : bool collectFilterPatterns = false;
69 :
70 : // Mandatory
71 197 : auto source_it = j.find("source");
72 197 : std::string sourceSpec = *source_it;
73 :
74 197 : auto target_it = j.find("target");
75 197 : std::string targetSpec = *target_it;
76 :
77 : // Optional
78 197 : auto it = j.find("filter");
79 197 : filter_ = "";
80 197 : has_filter_ = false;
81 197 : if (it != j.end()) {
82 35 : has_filter_ = true;
83 :
84 : // [filter_type_]
85 : // RegexCapture regular expression literal -> [filter_rgx_]
86 : // RegexReplace regular expression literal (rgx) -> [filter_rgx_] / replace format (fmt) -> [filter_]
87 : // Append suffix value -> [filter_]
88 : // Prepend prefix value -> [filter_]
89 : // Sum amount -> [filter_i_/filter_u_/filter_f_/filter_number_type_]
90 : // Multiply amount -> [filter_i_/filter_u_/filter_f_/filter_number_type_]
91 : // ConditionVar variable name -> [filter_]
92 : // EqualTo value -> [filter_]
93 : // DifferentFrom value -> [filter_]
94 : // JsonConstraint value -> [filter_object_]
95 : // SchemaId value -> [filter_]
96 :
97 35 : auto f_it = it->find("RegexCapture");
98 :
99 : try {
100 35 : if (f_it != it->end()) {
101 6 : filter_ = *f_it;
102 6 : filter_rgx_.assign(filter_, std::regex::optimize);
103 4 : filter_type_ = FilterType::RegexCapture;
104 : }
105 29 : else if ((f_it = it->find("RegexReplace")) != it->end()) {
106 2 : filter_rgx_.assign(std::string(*(f_it->find("rgx"))), std::regex::optimize);
107 2 : filter_ = *(f_it->find("fmt"));
108 2 : filter_type_ = FilterType::RegexReplace;
109 : }
110 27 : else if ((f_it = it->find("Append")) != it->end()) {
111 2 : filter_ = *f_it;
112 2 : filter_type_ = FilterType::Append;
113 2 : collectFilterPatterns = true;
114 : }
115 25 : else if ((f_it = it->find("Prepend")) != it->end()) {
116 2 : filter_ = *f_it;
117 2 : filter_type_ = FilterType::Prepend;
118 2 : collectFilterPatterns = true;
119 : }
120 23 : else if ((f_it = it->find("Sum")) != it->end()) {
121 5 : if (f_it->is_number_unsigned()) { // first unsigned, because positive would be integer
122 3 : filter_u_ = *f_it;
123 3 : filter_number_type_ = 1 ;
124 : }
125 2 : else if (f_it->is_number_integer()) {
126 1 : filter_i_ = *f_it;
127 1 : filter_number_type_ = 0 ;
128 : }
129 1 : else if (f_it->is_number_float()) {
130 1 : filter_f_ = *f_it;
131 1 : filter_number_type_ = 2 ;
132 : }
133 5 : filter_type_ = FilterType::Sum;
134 : }
135 18 : else if ((f_it = it->find("Multiply")) != it->end()) {
136 3 : if (f_it->is_number_unsigned()) { // first unsigned, because positive would be integer
137 1 : filter_u_ = *f_it;
138 1 : filter_number_type_ = 1 ;
139 : }
140 2 : else if (f_it->is_number_integer()) {
141 1 : filter_i_ = *f_it;
142 1 : filter_number_type_ = 0 ;
143 : }
144 1 : else if (f_it->is_number_float()) {
145 1 : filter_f_ = *f_it;
146 1 : filter_number_type_ = 2 ;
147 : }
148 3 : filter_type_ = FilterType::Multiply;
149 : }
150 15 : else if ((f_it = it->find("ConditionVar")) != it->end()) {
151 4 : filter_ = *f_it;
152 4 : filter_type_ = FilterType::ConditionVar;
153 : }
154 11 : else if ((f_it = it->find("EqualTo")) != it->end()) {
155 3 : filter_ = *f_it;
156 3 : filter_type_ = FilterType::EqualTo;
157 3 : collectFilterPatterns = true;
158 : }
159 8 : else if ((f_it = it->find("DifferentFrom")) != it->end()) {
160 3 : filter_ = *f_it;
161 3 : filter_type_ = FilterType::DifferentFrom;
162 3 : collectFilterPatterns = true;
163 : }
164 5 : else if ((f_it = it->find("JsonConstraint")) != it->end()) {
165 3 : filter_object_ = *f_it;
166 3 : filter_type_ = FilterType::JsonConstraint;
167 : }
168 2 : else if ((f_it = it->find("SchemaId")) != it->end()) {
169 2 : filter_ = *f_it;
170 2 : filter_type_ = FilterType::SchemaId;
171 : }
172 : }
173 2 : catch (std::regex_error &e) {
174 2 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
175 2 : return false;
176 2 : }
177 : }
178 :
179 : // Interpret source/target:
180 :
181 : // SOURCE (enum SourceType { RequestUri = 0, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser,
182 : // Math, Random, RandomSet, Timestamp, Strftime, Recvseq, SVar, SGVar, Value, ServerEvent, InState };)
183 195 : source_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
184 :
185 : // Source specifications:
186 : // - request.uri: whole `url-decoded` request *URI* (path together with possible query parameters). This is the unmodified original *URI*, not necessarily the same as the classification *URI*.
187 : // - request.uri.path: `url-decoded` request *URI* path part.
188 : // + request.uri.param.<name>: request URI specific parameter `<name>`.
189 : // - request.body: request body document.
190 : // + request.body./<node1>/../<nodeN>: request body node path.
191 : // + request.header.<hname>: request header component (i.e. *content-type*).
192 : // - eraser: this is used to indicate that the *target* specified (next section) must be removed or reset.
193 : // + math.`<expression>`: this source is based in Arash Partow's exprtk math library compilation.
194 : // + random.<min>.<max>: integer number in range `[min, max]`. Negatives allowed, i.e.: `"-3.+4"`.
195 : // + timestamp.<unit>: UNIX epoch time in `s` (seconds), `ms` (milliseconds), `us` (microseconds) or `ns` (nanoseconds).
196 : // + strftime.<format>: current date/time formatted by [strftime](https://www.cplusplus.com/reference/ctime/strftime/).
197 : // - recvseq: sequence id increased for every mock reception (starts on *1* when the *h2agent* is started).
198 : // + var.<id>: general purpose variable.
199 : // + globalVar.<id>: general purpose global variable.
200 : // - value.<value>: free string value. Even convertible types are allowed, for example: integer string, unsigned integer string, float number string, boolean string (true if non-empty string), will be converted to the target type.
201 : // - inState: current processing state.
202 : // + serverEvent.`<server event address in query parameters format>`: access server context indexed by request *method* (`requestMethod`), *URI* (`requestUri`), events *number* (`eventNumber`) and events number *path* (`eventPath`).
203 : // + txtFile.`<path>`: reads text content from file with the path provided.
204 : // + binFile.`<path>`: reads binary content from file with the path provided.
205 : // + command.`<command>`: executes command on process shell and captures the standard output.
206 :
207 : // Regex needed:
208 195 : static std::regex requestUriParam("^request.uri.param.(.*)", std::regex::optimize); // no need to escape dots as this is validated in schema
209 195 : static std::regex requestBodyNode("^request.body.(.*)", std::regex::optimize);
210 195 : static std::regex responseBodyNode("^response.body.(.*)", std::regex::optimize);
211 195 : static std::regex requestHeader("^request.header.(.*)", std::regex::optimize);
212 195 : static std::regex math("^math.(.*)", std::regex::optimize);
213 195 : static std::regex random("^random\\.([-+]{0,1}[0-9]+)\\.([-+]{0,1}[0-9]+)$", std::regex::optimize); // no need to validate min/max as it was done at schema
214 195 : static std::regex randomSet("^randomset.(.*)", std::regex::optimize);
215 195 : static std::regex timestamp("^timestamp.(.*)", std::regex::optimize); // no need to validate s/ms/us/ns as it was done at schema
216 195 : static std::regex strftime("^strftime.(.*)", std::regex::optimize); // free format, errors captured
217 195 : static std::regex varId("^var.(.*)", std::regex::optimize);
218 195 : static std::regex gvarId("^globalVar.(.*)", std::regex::optimize);
219 195 : static std::regex value("^value.([.\\s\\S]*)", std::regex::optimize); // added support for special characters: \n \t \r
220 195 : static std::regex serverEvent("^serverEvent.(.*)", std::regex::optimize);
221 195 : static std::regex clientEvent("^clientEvent.(.*)", std::regex::optimize);
222 195 : static std::regex responseHeader_source("^response.header.(.*)", std::regex::optimize);
223 195 : static std::regex txtFile("^txtFile.(.*)", std::regex::optimize);
224 195 : static std::regex binFile("^binFile.(.*)", std::regex::optimize);
225 195 : static std::regex command("^command.(.*)", std::regex::optimize);
226 :
227 195 : std::smatch matches; // to capture regex group(s)
228 : // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
229 : // In this case, it is not a problem, as we store the match from sourceSpec or targetSpec before changing them.
230 :
231 : // no need to try (controlled regex)
232 : //try {
233 195 : if (sourceSpec == "request.uri") {
234 5 : source_type_ = SourceType::RequestUri;
235 : }
236 190 : else if (sourceSpec == "request.uri.path") {
237 3 : source_type_ = SourceType::RequestUriPath;
238 : }
239 187 : else if (std::regex_match(sourceSpec, matches, requestUriParam)) { // parameter name
240 2 : source_ = matches.str(1);
241 2 : source_type_ = SourceType::RequestUriParam;
242 : }
243 185 : else if (sourceSpec == "request.body") { // whole document
244 13 : source_type_ = SourceType::RequestBody;
245 : }
246 172 : else if (std::regex_match(sourceSpec, matches, requestBodyNode)) { // nlohmann::json_pointer path
247 4 : source_ = matches.str(1);
248 4 : source_type_ = SourceType::RequestBody;
249 : }
250 168 : else if (sourceSpec == "response.body") { // whole document
251 2 : source_type_ = SourceType::ResponseBody;
252 : }
253 166 : else if (std::regex_match(sourceSpec, matches, responseBodyNode)) { // nlohmann::json_pointer path
254 2 : source_ = matches.str(1);
255 2 : source_type_ = SourceType::ResponseBody;
256 : }
257 164 : else if (std::regex_match(sourceSpec, matches, requestHeader)) { // header name
258 2 : source_ = matches.str(1);
259 2 : source_type_ = SourceType::RequestHeader;
260 : }
261 162 : else if (sourceSpec == "eraser") {
262 13 : source_type_ = SourceType::Eraser;
263 : }
264 149 : else if (std::regex_match(sourceSpec, matches, math)) { // math expression, i.e. "2*sqrt(2)"
265 4 : source_ = matches.str(1);
266 4 : source_type_ = SourceType::Math;
267 : }
268 145 : else if (std::regex_match(sourceSpec, matches, random)) { // range "<min>.<max>", i.e.: "-3.8", "0.100", "-15.+2", etc. These go to -> [source_i1_] and [source_i2_]
269 8 : source_i1_ = stoi(matches.str(1));
270 8 : source_i2_ = stoi(matches.str(2));
271 8 : source_type_ = SourceType::Random;
272 : }
273 137 : else if (std::regex_match(sourceSpec, matches, randomSet)) { // random set given by tokenized pipe-separated list of values
274 1 : source_ = matches.str(1) + "|"; // add pipe, just to allow getting empty part after trailing pipe ("foo|" => 'foo' ''), because the algorithm below ignores latest pipe and its trailing token.
275 1 : static std::regex pipedRgx(R"(\|)", std::regex::optimize);
276 2 : source_tokenized_ = std::vector<std::string>(
277 4 : std::sregex_token_iterator{begin(source_), end(source_), pipedRgx, -1},
278 2 : std::sregex_token_iterator{}
279 1 : );
280 1 : source_type_ = SourceType::RandomSet;
281 1 : source_.pop_back(); // remove added pipe
282 : }
283 136 : else if (std::regex_match(sourceSpec, matches, timestamp)) { // unit (s: seconds, ms: milliseconds, us: microseconds, ns: nanoseconds)
284 5 : source_ = matches.str(1);
285 5 : source_type_ = SourceType::Timestamp;
286 : }
287 131 : else if (std::regex_match(sourceSpec, matches, strftime)) { // current date/time formatted by as described in https://www.cplusplus.com/reference/ctime/strftime/
288 1 : source_ = matches.str(1);
289 1 : source_type_ = SourceType::Strftime;
290 : }
291 130 : else if (sourceSpec == "recvseq") {
292 2 : source_type_ = SourceType::Recvseq;
293 : }
294 128 : else if (sourceSpec == "sendseq") {
295 0 : source_type_ = SourceType::Sendseq;
296 : }
297 128 : else if (sourceSpec == "seq") {
298 0 : source_type_ = SourceType::Seq;
299 : }
300 128 : else if (std::regex_match(sourceSpec, matches, varId)) { // variable id
301 19 : source_ = matches.str(1);
302 19 : source_type_ = SourceType::SVar;
303 : }
304 109 : else if (std::regex_match(sourceSpec, matches, gvarId)) { // global variable id
305 12 : source_ = matches.str(1);
306 12 : source_type_ = SourceType::SGVar;
307 : }
308 97 : else if (std::regex_match(sourceSpec, matches, value)) { // value content
309 73 : source_ = matches.str(1);
310 73 : source_type_ = SourceType::Value;
311 : }
312 24 : else if (std::regex_match(sourceSpec, matches, serverEvent)) { // value content
313 5 : source_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3&eventPath=/requestBody
314 5 : source_type_ = SourceType::ServerEvent;
315 5 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(source_);
316 5 : std::map<std::string, std::string>::const_iterator it;
317 25 : for (auto const & qp: {
318 : "requestMethod", "requestUri", "eventNumber", "eventPath"
319 30 : }) { // tokenized vector order
320 20 : it = qmap.find(qp);
321 34 : source_tokenized_.push_back((it != qmap.end()) ? it->second:"");
322 : }
323 5 : }
324 19 : else if (std::regex_match(sourceSpec, matches, clientEvent)) { // client event data
325 4 : source_ = matches.str(1); // i.e. clientEndpointId=myEndpoint&requestMethod=GET&requestUri=/app/v1/foo/bar&eventNumber=3&eventPath=/responseBody
326 4 : source_type_ = SourceType::ClientEvent;
327 4 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(source_);
328 4 : std::map<std::string, std::string>::const_iterator it;
329 24 : for (auto const & qp: {
330 : "clientEndpointId", "requestMethod", "requestUri", "eventNumber", "eventPath"
331 28 : }) { // tokenized vector order
332 20 : it = qmap.find(qp);
333 28 : source_tokenized_.push_back((it != qmap.end()) ? it->second:"");
334 : }
335 4 : }
336 15 : else if (sourceSpec == "response.statusCode") {
337 2 : source_type_ = SourceType::ResponseStatusCode;
338 : }
339 13 : else if (std::regex_match(sourceSpec, matches, responseHeader_source)) { // response header name
340 1 : source_ = matches.str(1);
341 1 : source_type_ = SourceType::ResponseHeader;
342 : }
343 12 : else if (sourceSpec == "inState") {
344 4 : source_type_ = SourceType::InState;
345 : }
346 8 : else if (std::regex_match(sourceSpec, matches, txtFile)) { // path file
347 3 : source_ = matches.str(1);
348 3 : source_type_ = SourceType::STxtFile;
349 : }
350 5 : else if (std::regex_match(sourceSpec, matches, binFile)) { // path file
351 2 : source_ = matches.str(1);
352 2 : source_type_ = SourceType::SBinFile;
353 : }
354 3 : else if (std::regex_match(sourceSpec, matches, command)) { // command string
355 3 : source_ = matches.str(1);
356 3 : source_type_ = SourceType::Command;
357 : }
358 : // PROTECTED BY SCHEMA:
359 : //else { // some things could reach this (strange characters within value.* for example):
360 : // ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify source type for: %s", sourceSpec.c_str()), ERT_FILE_LOCATION);
361 : // return false;
362 : //}
363 : //}
364 : //catch (std::regex_error &e) {
365 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
366 : // return false;
367 : //}
368 :
369 : // TARGET (enum TargetType { ResponseBodyString = 0, ..., ResponseHeader_t, ResponseStatusCode_t, ..., RequestHeader, RequestDelayMs, RequestTimeoutMs, ClientEventToPurge };)
370 195 : target_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
371 195 : target2_ = ""; // same
372 :
373 : // Target specifications:
374 : // SERVER MODE
375 : // - response.body.string *[string]*: response body storing expected string processed.
376 : // - response.body.hexstring *[string]*: response body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
377 : // - response.body.json.string *[string]*: response body document storing expected string.
378 : // - response.body.json.integer *[number]*: response body document storing expected integer.
379 : // - response.body.json.unsigned *[unsigned number]*: response body document storing expected unsigned integer.
380 : // - response.body.json.float *[float number]*: response body document storing expected float number.
381 : // - response.body.json.boolean *[boolean]*: response body document storing expected booolean.
382 : // - response.body.json.object *[json object]*: response body document storing expected object.
383 : // - response.body.json.jsonstring *[json string]*: response body document storing expected object, extracted from json-parsed string, as root node.
384 : // + response.body.json.string./<n1>/../<nN> *[string]*: response body node path storing expected string.
385 : // + response.body.json.integer./<n1>/../<nN> *[number]*: response body node path storing expected integer.
386 : // + response.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: response body node path storing expected unsigned integer.
387 : // + response.body.json.float./<n1>/../<nN> *[float number]*: response body node path storing expected float number.
388 : // + response.body.json.boolean./<n1>/../<nN> *[boolean]*: response body node path storing expected booblean.
389 : // + response.body.json.object./<n1>/../<nN> *[json object]*: response body node path storing expected object.
390 : // + response.body.json.jsonstring./<n1>/../<nN> *[json string]*: response body node path storing expected object, extracted from json-parsed string, under provided path.
391 : // + response.header.<hname> *[string (or number as string)]*: response header component (i.e. *location*).
392 : // - response.statusCode *[unsigned integer]*: response status code.
393 : // - response.delayMs *[unsigned integer]*: simulated delay to respond.
394 : // + var.<id> *[string (or number as string)]*: general purpose variable.
395 : // + globalVar.<id> *[string (or number as string)]*: general purpose global variable.
396 : // - outState *[string (or number as string)]*: next processing state. This overrides the default provisioned one.
397 : // + outState.`[POST|GET|PUT|DELETE|HEAD][.<uri>]` *[string (or number as string)]*: next processing state for specific method (virtual server data will be created if needed: this way we could modify the flow for other methods different than the one which is managing the current provision). This target **admits variables substitution** in the `uri` part.
398 : // + txtFile.`<path>` *[string]*: dumps source (as string) over text file with the path provided.
399 : // + binFile.`<path>` *[string]*: dumps source (as string) over binary file with the path provided.
400 : // + udpSocket.`<path>[|<milliseconds delay>]` *[string]*: sends source (as string) towards the UDP unix socket with the path provided.
401 : // + serverEvent.`<server event address in query parameters format>`: this target is always used in conjunction with `eraser`.
402 : // - break *[string]*: when non-empty string is transferred, the transformations list is interrupted. Empty string (or undefined source) ignores the action.
403 : //
404 : // CLIENT MODE ADDITIONAL TARGET TYPES:
405 : // - request.body.string *[string]*: request body storing expected string processed.
406 : // - request.body.hexstring *[string]*: request body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
407 : // - request.body.json.string *[string]*: request body document storing expected string.
408 : // - request.body.json.integer *[number]*: request body document storing expected integer.
409 : // - request.body.json.unsigned *[unsigned number]*: request body document storing expected unsigned integer.
410 : // - request.body.json.float *[float number]*: request body document storing expected float number.
411 : // - request.body.json.boolean *[boolean]*: request body document storing expected booolean.
412 : // - request.body.json.object *[json object]*: request body document storing expected object.
413 : // - request.body.json.jsonstring *[json string]*: request body document storing expected object, extracted from json-parsed string, as root node.
414 : // + request.body.json.string./<n1>/../<nN> *[string]*: request body node path storing expected string.
415 : // + request.body.json.integer./<n1>/../<nN> *[number]*: request body node path storing expected integer.
416 : // + request.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: request body node path storing expected unsigned integer.
417 : // + request.body.json.float./<n1>/../<nN> *[float number]*: request body node path storing expected float number.
418 : // + request.body.json.boolean./<n1>/../<nN> *[boolean]*: request body node path storing expected booblean.
419 : // + request.body.json.object./<n1>/../<nN> *[json object]*: request body node path storing expected object.
420 : // + request.body.json.jsonstring./<n1>/../<nN> *[json string]*: request body node path storing expected object, extracted from json-parsed string, under provided path.
421 :
422 : // Regex needed:
423 : // SERVER MODE:
424 195 : static std::regex responseBodyJson_StringNode("^response.body.json.string.(.*)", std::regex::optimize);
425 195 : static std::regex responseBodyJson_IntegerNode("^response.body.json.integer.(.*)", std::regex::optimize);
426 195 : static std::regex responseBodyJson_UnsignedNode("^response.body.json.unsigned.(.*)", std::regex::optimize);
427 195 : static std::regex responseBodyJson_FloatNode("^response.body.json.float.(.*)", std::regex::optimize);
428 195 : static std::regex responseBodyJson_BooleanNode("^response.body.json.boolean.(.*)", std::regex::optimize);
429 195 : static std::regex responseBodyJson_ObjectNode("^response.body.json.object.(.*)", std::regex::optimize);
430 195 : static std::regex responseBodyJson_JsonStringNode("^response.body.json.jsonstring.(.*)", std::regex::optimize);
431 195 : static std::regex responseHeader("^response.header.(.*)", std::regex::optimize);
432 195 : static std::regex outStateMethodUri("^outState.(POST|GET|PUT|DELETE|HEAD)(\\..+)?", std::regex::optimize);
433 :
434 : // CLIENT MODE:
435 195 : static std::regex requestBodyJson_StringNode("^request.body.json.string.(.*)", std::regex::optimize);
436 195 : static std::regex requestBodyJson_IntegerNode("^request.body.json.integer.(.*)", std::regex::optimize);
437 195 : static std::regex requestBodyJson_UnsignedNode("^request.body.json.unsigned.(.*)", std::regex::optimize);
438 195 : static std::regex requestBodyJson_FloatNode("^request.body.json.float.(.*)", std::regex::optimize);
439 195 : static std::regex requestBodyJson_BooleanNode("^request.body.json.boolean.(.*)", std::regex::optimize);
440 195 : static std::regex requestBodyJson_ObjectNode("^request.body.json.object.(.*)", std::regex::optimize);
441 195 : static std::regex requestBodyJson_JsonStringNode("^request.body.json.jsonstring.(.*)", std::regex::optimize);
442 :
443 : // Only target
444 195 : static std::regex requestHeader_target("^request.header.(.*)", std::regex::optimize);
445 195 : static std::regex udpSocket("^udpSocket.(.*)", std::regex::optimize);
446 195 : static std::regex clientProvision("^clientProvision.(.*)", std::regex::optimize);
447 :
448 : // no need to try (controlled regex)
449 : //try {
450 : // SERVER_MODE
451 195 : if (targetSpec == "response.body.string") {
452 64 : target_type_ = TargetType::ResponseBodyString;
453 : }
454 131 : else if (targetSpec == "response.body.hexstring") {
455 1 : target_type_ = TargetType::ResponseBodyHexString;
456 : }
457 130 : else if (targetSpec == "response.body.json.string") { // whole document
458 2 : target_type_ = TargetType::ResponseBodyJson_String;
459 : }
460 128 : else if (std::regex_match(targetSpec, matches, responseBodyJson_StringNode)) { // nlohmann::json_pointer path
461 16 : target_ = matches.str(1);
462 16 : target_type_ = TargetType::ResponseBodyJson_String;
463 : }
464 112 : else if (targetSpec == "response.body.json.integer") { // whole document
465 1 : target_type_ = TargetType::ResponseBodyJson_Integer;
466 : }
467 111 : else if (std::regex_match(targetSpec, matches, responseBodyJson_IntegerNode)) { // nlohmann::json_pointer path
468 5 : target_ = matches.str(1);
469 5 : target_type_ = TargetType::ResponseBodyJson_Integer;
470 : }
471 106 : else if (targetSpec == "response.body.json.unsigned") { // whole document
472 1 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
473 : }
474 105 : else if (std::regex_match(targetSpec, matches, responseBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
475 1 : target_ = matches.str(1);
476 1 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
477 : }
478 104 : else if (targetSpec == "response.body.json.float") { // whole document
479 1 : target_type_ = TargetType::ResponseBodyJson_Float;
480 : }
481 103 : else if (std::regex_match(targetSpec, matches, responseBodyJson_FloatNode)) { // nlohmann::json_pointer path
482 1 : target_ = matches.str(1);
483 1 : target_type_ = TargetType::ResponseBodyJson_Float;
484 : }
485 102 : else if (targetSpec == "response.body.json.boolean") { // whole document
486 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
487 : }
488 101 : else if (std::regex_match(targetSpec, matches, responseBodyJson_BooleanNode)) { // nlohmann::json_pointer path
489 1 : target_ = matches.str(1);
490 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
491 : }
492 100 : else if (targetSpec == "response.body.json.object") { // whole document
493 7 : target_type_ = TargetType::ResponseBodyJson_Object;
494 : }
495 93 : else if (std::regex_match(targetSpec, matches, responseBodyJson_ObjectNode)) { // nlohmann::json_pointer path
496 8 : target_ = matches.str(1);
497 8 : target_type_ = TargetType::ResponseBodyJson_Object;
498 : }
499 85 : else if (targetSpec == "response.body.json.jsonstring") { // whole document
500 2 : target_type_ = TargetType::ResponseBodyJson_JsonString;
501 : }
502 83 : else if (std::regex_match(targetSpec, matches, responseBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
503 1 : target_ = matches.str(1);
504 1 : target_type_ = TargetType::ResponseBodyJson_JsonString;
505 : }
506 : // CLIENT MODE
507 82 : else if (targetSpec == "request.body.string") {
508 0 : target_type_ = TargetType::RequestBodyString;
509 : }
510 82 : else if (targetSpec == "request.body.hexstring") {
511 0 : target_type_ = TargetType::RequestBodyHexString;
512 : }
513 82 : else if (targetSpec == "request.body.json.string") { // whole document
514 0 : target_type_ = TargetType::RequestBodyJson_String;
515 : }
516 82 : else if (std::regex_match(targetSpec, matches, requestBodyJson_StringNode)) { // nlohmann::json_pointer path
517 1 : target_ = matches.str(1);
518 1 : target_type_ = TargetType::RequestBodyJson_String;
519 : }
520 81 : else if (targetSpec == "request.body.json.integer") { // whole document
521 0 : target_type_ = TargetType::RequestBodyJson_Integer;
522 : }
523 81 : else if (std::regex_match(targetSpec, matches, requestBodyJson_IntegerNode)) { // nlohmann::json_pointer path
524 1 : target_ = matches.str(1);
525 1 : target_type_ = TargetType::RequestBodyJson_Integer;
526 : }
527 80 : else if (targetSpec == "request.body.json.unsigned") { // whole document
528 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
529 : }
530 80 : else if (std::regex_match(targetSpec, matches, requestBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
531 0 : target_ = matches.str(1);
532 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
533 : }
534 80 : else if (targetSpec == "request.body.json.float") { // whole document
535 0 : target_type_ = TargetType::RequestBodyJson_Float;
536 : }
537 80 : else if (std::regex_match(targetSpec, matches, requestBodyJson_FloatNode)) { // nlohmann::json_pointer path
538 0 : target_ = matches.str(1);
539 0 : target_type_ = TargetType::RequestBodyJson_Float;
540 : }
541 80 : else if (targetSpec == "request.body.json.boolean") { // whole document
542 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
543 : }
544 80 : else if (std::regex_match(targetSpec, matches, requestBodyJson_BooleanNode)) { // nlohmann::json_pointer path
545 0 : target_ = matches.str(1);
546 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
547 : }
548 80 : else if (targetSpec == "request.body.json.object") { // whole document
549 0 : target_type_ = TargetType::RequestBodyJson_Object;
550 : }
551 80 : else if (std::regex_match(targetSpec, matches, requestBodyJson_ObjectNode)) { // nlohmann::json_pointer path
552 0 : target_ = matches.str(1);
553 0 : target_type_ = TargetType::RequestBodyJson_Object;
554 : }
555 80 : else if (targetSpec == "request.body.json.jsonstring") { // whole document
556 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
557 : }
558 80 : else if (std::regex_match(targetSpec, matches, requestBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
559 0 : target_ = matches.str(1);
560 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
561 : }
562 :
563 : // CLIENT MODE ADDITIONAL TARGET TYPES:
564 80 : else if (std::regex_match(targetSpec, matches, requestHeader_target)) { // request header name
565 2 : target_ = matches.str(1);
566 2 : target_type_ = TargetType::RequestHeader_t;
567 : }
568 78 : else if (targetSpec == "request.delayMs") {
569 4 : target_type_ = TargetType::RequestDelayMs;
570 : }
571 74 : else if (targetSpec == "request.timeoutMs") {
572 1 : target_type_ = TargetType::RequestTimeoutMs;
573 : }
574 73 : else if (targetSpec == "request.uri") {
575 11 : target_type_ = TargetType::RequestUri_t;
576 : }
577 62 : else if (targetSpec == "request.method") {
578 1 : target_type_ = TargetType::RequestMethod_t;
579 : }
580 :
581 61 : else if (std::regex_match(targetSpec, matches, responseHeader)) { // header name
582 1 : target_ = matches.str(1);
583 1 : target_type_ = TargetType::ResponseHeader_t;
584 : }
585 60 : else if (targetSpec == "response.statusCode") {
586 6 : target_type_ = TargetType::ResponseStatusCode_t;
587 : }
588 54 : else if (targetSpec == "response.delayMs") {
589 1 : target_type_ = TargetType::ResponseDelayMs;
590 : }
591 53 : else if (std::regex_match(targetSpec, matches, varId)) { // variable id
592 17 : target_ = matches.str(1);
593 17 : target_type_ = TargetType::TVar;
594 : }
595 36 : else if (std::regex_match(targetSpec, matches, gvarId)) { // global variable id
596 10 : target_ = matches.str(1);
597 10 : target_type_ = TargetType::TGVar;
598 : }
599 26 : else if (targetSpec == "outState") {
600 6 : target_type_ = TargetType::OutState;
601 : }
602 20 : else if (std::regex_match(targetSpec, matches, outStateMethodUri)) { // method
603 2 : target_ = matches.str(1); // <method>
604 2 : target2_ = matches.str(2); // .<uri>
605 2 : if (!target2_.empty()) {
606 2 : target2_ = target2_.substr(1); // remove the initial dot to store the uri
607 : }
608 2 : target_type_ = TargetType::OutState;
609 : }
610 18 : else if (std::regex_match(targetSpec, matches, txtFile)) { // path file
611 3 : target_ = matches.str(1);
612 3 : target_type_ = TargetType::TTxtFile;
613 : }
614 15 : else if (std::regex_match(targetSpec, matches, binFile)) { // path file
615 2 : target_ = matches.str(1);
616 2 : target_type_ = TargetType::TBinFile;
617 : }
618 13 : else if (std::regex_match(targetSpec, matches, udpSocket)) { // path file
619 1 : target_ = matches.str(1);
620 1 : target_type_ = TargetType::UDPSocket;
621 : }
622 12 : else if (std::regex_match(targetSpec, matches, serverEvent)) { // value content
623 4 : target_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3
624 4 : target_type_ = TargetType::ServerEventToPurge;
625 4 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(target_);
626 4 : std::map<std::string, std::string>::const_iterator it;
627 16 : for (auto const & qp: {
628 : "requestMethod", "requestUri", "eventNumber"
629 20 : }) { // tokenized vector order
630 12 : it = qmap.find(qp);
631 20 : target_tokenized_.push_back((it != qmap.end()) ? it->second:"");
632 : }
633 4 : }
634 8 : else if (std::regex_match(targetSpec, matches, clientEvent)) { // client event purge
635 1 : target_ = matches.str(1); // i.e. clientEndpointId=myEndpoint&requestMethod=GET&requestUri=/app/v1/foo/bar&eventNumber=3
636 1 : target_type_ = TargetType::ClientEventToPurge;
637 1 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(target_);
638 1 : std::map<std::string, std::string>::const_iterator it;
639 5 : for (auto const & qp: {
640 : "clientEndpointId", "requestMethod", "requestUri", "eventNumber"
641 6 : }) { // tokenized vector order
642 4 : it = qmap.find(qp);
643 12 : target_tokenized_.push_back((it != qmap.end()) ? it->second:"");
644 : }
645 1 : }
646 7 : else if (targetSpec == "break") {
647 4 : target_type_ = TargetType::Break;
648 : }
649 3 : else if (std::regex_match(targetSpec, matches, clientProvision)) { // client provision id
650 3 : target_ = matches.str(1);
651 3 : target_type_ = TargetType::ClientProvision_t;
652 : }
653 : // PROTECTED BY SCHEMA:
654 : //else { // very strange to reach this:
655 : // ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify target type for: %s", targetSpec.c_str()), ERT_FILE_LOCATION);
656 : // return false;
657 : //}
658 : //}
659 : //catch (std::regex_error &e) {
660 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
661 : // return false;
662 : //}
663 :
664 : //LOGDEBUG(ert::tracing::Logger::debug(asString(), ERT_FILE_LOCATION));
665 :
666 : // Variable patterns:
667 195 : collectVariablePatterns(source_, source_patterns_);
668 195 : if (collectFilterPatterns) collectVariablePatterns(filter_, filter_patterns_); // protected to avoid possible gathering of false patterns (i.e. complex regexp's)
669 195 : collectVariablePatterns(target_, target_patterns_);
670 195 : collectVariablePatterns(target2_, target2_patterns_);
671 :
672 195 : return true;
673 197 : }
674 :
675 11 : std::string Transformation::asString() const {
676 :
677 11 : std::stringstream ss;
678 :
679 :
680 : // SOURCE
681 11 : ss << "SourceType: " << SourceTypeAsText(source_type_);
682 11 : if (source_type_ != SourceType::RequestUri && source_type_ != SourceType::RequestUriPath && source_type_ != SourceType::Eraser && source_type_ != SourceType::Recvseq && source_type_ != SourceType::Sendseq && source_type_ != SourceType::Seq && source_type_ != SourceType::InState && source_type_ != SourceType::ResponseStatusCode) {
683 9 : ss << " | source_: " << source_;
684 :
685 9 : if (source_type_ == SourceType::RequestBody || source_type_ == SourceType::ResponseBody) {
686 1 : ss << " (empty: whole, path: node)";
687 : }
688 8 : else if (source_type_ == SourceType::Random) {
689 1 : ss << " | source_i1_: " << source_i1_ << " (Random min)" << " | source_i2_: " << source_i2_ << " (Random max)";
690 : }
691 7 : else if (source_type_ == SourceType::RandomSet || source_type_ == SourceType::ServerEvent || source_type_ == SourceType::ClientEvent) {
692 0 : ss << " | source_tokenized_:";
693 0 : for(auto it: source_tokenized_) {
694 0 : ss << " '" << it << "'";
695 0 : }
696 0 : }
697 7 : else if (source_type_ == SourceType::STxtFile || source_type_ == SourceType::SBinFile) {
698 1 : ss << " (path file)";
699 : }
700 6 : else if (source_type_ == SourceType::Command) {
701 1 : ss << " (shell command expression)";
702 : }
703 :
704 9 : if (!source_patterns_.empty()) {
705 1 : ss << " | source variables:";
706 2 : for (auto it = source_patterns_.begin(); it != source_patterns_.end(); it ++) {
707 1 : ss << " " << it->second;
708 : }
709 : }
710 : }
711 :
712 : // TARGET
713 11 : ss << " | TargetType: " << TargetTypeAsText(target_type_);
714 11 : if (target_type_ != TargetType::ResponseStatusCode_t &&
715 11 : target_type_ != TargetType::ResponseDelayMs &&
716 11 : target_type_ != TargetType::ResponseBodyString &&
717 4 : target_type_ != TargetType::ResponseBodyHexString &&
718 4 : target_type_ != TargetType::RequestDelayMs &&
719 4 : target_type_ != TargetType::RequestTimeoutMs &&
720 4 : target_type_ != TargetType::RequestBodyString &&
721 4 : target_type_ != TargetType::RequestBodyHexString ) {
722 :
723 4 : ss << " | target_: " << target_;
724 :
725 4 : if (target_type_ == TargetType::ResponseBodyJson_String || target_type_ == TargetType::ResponseBodyJson_Integer || target_type_ == TargetType::ResponseBodyJson_Unsigned || target_type_ == TargetType::ResponseBodyJson_Float || target_type_ == TargetType::ResponseBodyJson_Boolean || target_type_ == TargetType::ResponseBodyJson_Object ||
726 3 : target_type_ == TargetType::RequestBodyJson_String || target_type_ == TargetType::RequestBodyJson_Integer || target_type_ == TargetType::RequestBodyJson_Unsigned || target_type_ == TargetType::RequestBodyJson_Float || target_type_ == TargetType::RequestBodyJson_Boolean || target_type_ == TargetType::RequestBodyJson_Object) {
727 1 : ss << " (empty: whole, path: node)";
728 : }
729 3 : else if (target_type_ == TargetType::OutState) {
730 1 : ss << " (empty: current method, method: another)" << " | target2_: " << target2_ << "(empty: current uri, uri: another)";
731 1 : if (!target2_patterns_.empty()) {
732 1 : ss << " | target2 variables:";
733 2 : for (auto it = target2_patterns_.begin(); it != target2_patterns_.end(); it ++) {
734 1 : ss << " " << it->second;
735 : }
736 : }
737 : }
738 2 : else if (target_type_ == TargetType::TTxtFile || target_type_ == TargetType::TBinFile) {
739 1 : ss << " (path file)";
740 : }
741 1 : else if (target_type_ == TargetType::UDPSocket) {
742 0 : ss << " (<path file>[|<write delay ms>])";
743 : }
744 :
745 4 : if (!target_patterns_.empty()) {
746 1 : ss << " | target variables:";
747 2 : for (auto it = target_patterns_.begin(); it != target_patterns_.end(); it ++) {
748 1 : ss << " " << it->second;
749 : }
750 : }
751 : }
752 :
753 : // FILTER
754 11 : if (has_filter_) {
755 4 : ss << " | FilterType: " << FilterTypeAsText(filter_type_);
756 4 : if (filter_type_ != FilterType::Sum && filter_type_ != FilterType::Multiply) {
757 : /*<< " | filter_rgx_: ?"*/
758 3 : ss << " | filter_ " << filter_;
759 :
760 3 : if (filter_type_ == FilterType::RegexReplace) {
761 1 : ss << " (fmt)";
762 : }
763 2 : else if (filter_type_ == FilterType::RegexCapture) {
764 1 : ss << " (literal, although not actually needed, but useful to access & print on traces)";
765 : }
766 : }
767 : else {
768 1 : ss << " | filter_number_type_: " << filter_number_type_ << " (0: integer, 1: unsigned, 2: float)"
769 1 : << " | filter_i_: " << filter_i_ << " | filter_u_: " << filter_u_ << " | filter_f_: " << filter_f_;
770 : }
771 :
772 4 : if (!filter_patterns_.empty()) {
773 1 : ss << " | filter variables:";
774 2 : for (auto it = filter_patterns_.begin(); it != filter_patterns_.end(); it ++) {
775 1 : ss << " " << it->second;
776 : }
777 : }
778 : }
779 :
780 22 : return ss.str();
781 11 : }
782 :
783 :
784 : }
785 : }
786 :
|