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 472 : void Transformation::collectVariablePatterns(const std::string &str, std::map<std::string, std::string> &patterns) {
51 :
52 472 : static std::regex re("@\\{[^\\{\\}]*\\}", std::regex::optimize); // @{[^{}]*} with curly braces escaped
53 : // or: R"(@\{[^\{\}]*\})"
54 :
55 472 : std::string::const_iterator it(str.cbegin());
56 472 : std::smatch matches;
57 472 : std::string pattern;
58 472 : patterns.clear();
59 485 : 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 472 : }
65 :
66 157 : bool Transformation::load(const nlohmann::json &j) {
67 :
68 157 : bool collectFilterPatterns = false;
69 :
70 : // Mandatory
71 157 : auto source_it = j.find("source");
72 157 : std::string sourceSpec = *source_it;
73 :
74 157 : auto target_it = j.find("target");
75 157 : std::string targetSpec = *target_it;
76 :
77 : // Optional
78 157 : auto it = j.find("filter");
79 157 : filter_ = "";
80 157 : has_filter_ = false;
81 157 : if (it != j.end()) {
82 31 : 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 31 : auto f_it = it->find("RegexCapture");
98 :
99 : try {
100 31 : 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 25 : 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 23 : else if ((f_it = it->find("Append")) != it->end()) {
111 1 : filter_ = *f_it;
112 1 : filter_type_ = FilterType::Append;
113 1 : collectFilterPatterns = true;
114 : }
115 22 : else if ((f_it = it->find("Prepend")) != it->end()) {
116 1 : filter_ = *f_it;
117 1 : filter_type_ = FilterType::Prepend;
118 1 : collectFilterPatterns = true;
119 : }
120 21 : else if ((f_it = it->find("Sum")) != it->end()) {
121 4 : if (f_it->is_number_unsigned()) { // first unsigned, because positive would be integer
122 2 : filter_u_ = *f_it;
123 2 : 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 4 : filter_type_ = FilterType::Sum;
134 : }
135 17 : 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 14 : else if ((f_it = it->find("ConditionVar")) != it->end()) {
151 4 : filter_ = *f_it;
152 4 : filter_type_ = FilterType::ConditionVar;
153 : }
154 10 : 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 7 : else if ((f_it = it->find("DifferentFrom")) != it->end()) {
160 2 : filter_ = *f_it;
161 2 : filter_type_ = FilterType::DifferentFrom;
162 2 : 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 155 : 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 155 : static std::regex requestUriParam("^request.uri.param.(.*)", std::regex::optimize); // no need to escape dots as this is validated in schema
209 155 : static std::regex requestBodyNode("^request.body.(.*)", std::regex::optimize);
210 155 : static std::regex responseBodyNode("^response.body.(.*)", std::regex::optimize);
211 155 : static std::regex requestHeader("^request.header.(.*)", std::regex::optimize);
212 155 : static std::regex math("^math.(.*)", std::regex::optimize);
213 155 : 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 155 : static std::regex randomSet("^randomset.(.*)", std::regex::optimize);
215 155 : static std::regex timestamp("^timestamp.(.*)", std::regex::optimize); // no need to validate s/ms/us/ns as it was done at schema
216 155 : static std::regex strftime("^strftime.(.*)", std::regex::optimize); // free format, errors captured
217 155 : static std::regex varId("^var.(.*)", std::regex::optimize);
218 155 : static std::regex gvarId("^globalVar.(.*)", std::regex::optimize);
219 155 : static std::regex value("^value.([.\\s\\S]*)", std::regex::optimize); // added support for special characters: \n \t \r
220 155 : static std::regex serverEvent("^serverEvent.(.*)", std::regex::optimize);
221 155 : static std::regex txtFile("^txtFile.(.*)", std::regex::optimize);
222 155 : static std::regex binFile("^binFile.(.*)", std::regex::optimize);
223 155 : static std::regex command("^command.(.*)", std::regex::optimize);
224 :
225 155 : std::smatch matches; // to capture regex group(s)
226 : // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
227 : // In this case, it is not a problem, as we store the match from sourceSpec or targetSpec before changing them.
228 :
229 : // no need to try (controlled regex)
230 : //try {
231 155 : if (sourceSpec == "request.uri") {
232 5 : source_type_ = SourceType::RequestUri;
233 : }
234 150 : else if (sourceSpec == "request.uri.path") {
235 3 : source_type_ = SourceType::RequestUriPath;
236 : }
237 147 : else if (std::regex_match(sourceSpec, matches, requestUriParam)) { // parameter name
238 2 : source_ = matches.str(1);
239 2 : source_type_ = SourceType::RequestUriParam;
240 : }
241 145 : else if (sourceSpec == "request.body") { // whole document
242 13 : source_type_ = SourceType::RequestBody;
243 : }
244 132 : else if (std::regex_match(sourceSpec, matches, requestBodyNode)) { // nlohmann::json_pointer path
245 3 : source_ = matches.str(1);
246 3 : source_type_ = SourceType::RequestBody;
247 : }
248 129 : else if (sourceSpec == "response.body") { // whole document
249 1 : source_type_ = SourceType::ResponseBody;
250 : }
251 128 : else if (std::regex_match(sourceSpec, matches, responseBodyNode)) { // nlohmann::json_pointer path
252 2 : source_ = matches.str(1);
253 2 : source_type_ = SourceType::ResponseBody;
254 : }
255 126 : else if (std::regex_match(sourceSpec, matches, requestHeader)) { // header name
256 2 : source_ = matches.str(1);
257 2 : source_type_ = SourceType::RequestHeader;
258 : }
259 124 : else if (sourceSpec == "eraser") {
260 7 : source_type_ = SourceType::Eraser;
261 : }
262 117 : else if (std::regex_match(sourceSpec, matches, math)) { // math expression, i.e. "2*sqrt(2)"
263 3 : source_ = matches.str(1);
264 3 : source_type_ = SourceType::Math;
265 : }
266 114 : 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_]
267 7 : source_i1_ = stoi(matches.str(1));
268 7 : source_i2_ = stoi(matches.str(2));
269 7 : source_type_ = SourceType::Random;
270 : }
271 107 : else if (std::regex_match(sourceSpec, matches, randomSet)) { // random set given by tokenized pipe-separated list of values
272 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.
273 1 : static std::regex pipedRgx(R"(\|)", std::regex::optimize);
274 2 : source_tokenized_ = std::vector<std::string>(
275 4 : std::sregex_token_iterator{begin(source_), end(source_), pipedRgx, -1},
276 2 : std::sregex_token_iterator{}
277 1 : );
278 1 : source_type_ = SourceType::RandomSet;
279 1 : source_.pop_back(); // remove added pipe
280 : }
281 106 : else if (std::regex_match(sourceSpec, matches, timestamp)) { // unit (s: seconds, ms: milliseconds, us: microseconds, ns: nanoseconds)
282 4 : source_ = matches.str(1);
283 4 : source_type_ = SourceType::Timestamp;
284 : }
285 102 : else if (std::regex_match(sourceSpec, matches, strftime)) { // current date/time formatted by as described in https://www.cplusplus.com/reference/ctime/strftime/
286 1 : source_ = matches.str(1);
287 1 : source_type_ = SourceType::Strftime;
288 : }
289 101 : else if (sourceSpec == "recvseq") {
290 2 : source_type_ = SourceType::Recvseq;
291 : }
292 99 : else if (std::regex_match(sourceSpec, matches, varId)) { // variable id
293 15 : source_ = matches.str(1);
294 15 : source_type_ = SourceType::SVar;
295 : }
296 84 : else if (std::regex_match(sourceSpec, matches, gvarId)) { // global variable id
297 9 : source_ = matches.str(1);
298 9 : source_type_ = SourceType::SGVar;
299 : }
300 75 : else if (std::regex_match(sourceSpec, matches, value)) { // value content
301 61 : source_ = matches.str(1);
302 61 : source_type_ = SourceType::Value;
303 : }
304 14 : else if (std::regex_match(sourceSpec, matches, serverEvent)) { // value content
305 3 : source_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3&eventPath=/requestBody
306 3 : source_type_ = SourceType::ServerEvent;
307 3 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(source_);
308 3 : std::map<std::string, std::string>::const_iterator it;
309 15 : for (auto qp: {
310 : "requestMethod", "requestUri", "eventNumber", "eventPath"
311 18 : }) { // tokenized vector order
312 12 : it = qmap.find(qp);
313 12 : source_tokenized_.push_back((it != qmap.end()) ? it->second:"");
314 : }
315 3 : }
316 11 : else if (sourceSpec == "inState") {
317 3 : source_type_ = SourceType::InState;
318 : }
319 8 : else if (std::regex_match(sourceSpec, matches, txtFile)) { // path file
320 3 : source_ = matches.str(1);
321 3 : source_type_ = SourceType::STxtFile;
322 : }
323 5 : else if (std::regex_match(sourceSpec, matches, binFile)) { // path file
324 2 : source_ = matches.str(1);
325 2 : source_type_ = SourceType::SBinFile;
326 : }
327 3 : else if (std::regex_match(sourceSpec, matches, command)) { // command string
328 3 : source_ = matches.str(1);
329 3 : source_type_ = SourceType::Command;
330 : }
331 : // PROTECTED BY SCHEMA:
332 : //else { // some things could reach this (strange characters within value.* for example):
333 : // ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify source type for: %s", sourceSpec.c_str()), ERT_FILE_LOCATION);
334 : // return false;
335 : //}
336 : //}
337 : //catch (std::regex_error &e) {
338 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
339 : // return false;
340 : //}
341 :
342 : // TARGET (enum TargetType { ResponseBodyString = 0, 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 };)
343 155 : target_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
344 155 : target2_ = ""; // same
345 :
346 : // Target specifications:
347 : // SERVER MODE
348 : // - response.body.string *[string]*: response body storing expected string processed.
349 : // - response.body.hexstring *[string]*: response body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
350 : // - response.body.json.string *[string]*: response body document storing expected string.
351 : // - response.body.json.integer *[number]*: response body document storing expected integer.
352 : // - response.body.json.unsigned *[unsigned number]*: response body document storing expected unsigned integer.
353 : // - response.body.json.float *[float number]*: response body document storing expected float number.
354 : // - response.body.json.boolean *[boolean]*: response body document storing expected booolean.
355 : // - response.body.json.object *[json object]*: response body document storing expected object.
356 : // - response.body.json.jsonstring *[json string]*: response body document storing expected object, extracted from json-parsed string, as root node.
357 : // + response.body.json.string./<n1>/../<nN> *[string]*: response body node path storing expected string.
358 : // + response.body.json.integer./<n1>/../<nN> *[number]*: response body node path storing expected integer.
359 : // + response.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: response body node path storing expected unsigned integer.
360 : // + response.body.json.float./<n1>/../<nN> *[float number]*: response body node path storing expected float number.
361 : // + response.body.json.boolean./<n1>/../<nN> *[boolean]*: response body node path storing expected booblean.
362 : // + response.body.json.object./<n1>/../<nN> *[json object]*: response body node path storing expected object.
363 : // + response.body.json.jsonstring./<n1>/../<nN> *[json string]*: response body node path storing expected object, extracted from json-parsed string, under provided path.
364 : // + response.header.<hname> *[string (or number as string)]*: response header component (i.e. *location*).
365 : // - response.statusCode *[unsigned integer]*: response status code.
366 : // - response.delayMs *[unsigned integer]*: simulated delay to respond.
367 : // + var.<id> *[string (or number as string)]*: general purpose variable.
368 : // + globalVar.<id> *[string (or number as string)]*: general purpose global variable.
369 : // - outState *[string (or number as string)]*: next processing state. This overrides the default provisioned one.
370 : // + 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.
371 : // + txtFile.`<path>` *[string]*: dumps source (as string) over text file with the path provided.
372 : // + binFile.`<path>` *[string]*: dumps source (as string) over binary file with the path provided.
373 : // + udpSocket.`<path>[|<milliseconds delay>]` *[string]*: sends source (as string) towards the UDP unix socket with the path provided.
374 : // + serverEvent.`<server event address in query parameters format>`: this target is always used in conjunction with `eraser`.
375 : // - break *[string]*: when non-empty string is transferred, the transformations list is interrupted. Empty string (or undefined source) ignores the action.
376 : //
377 : // CLIENT MODE ADDITIONAL TARGET TYPES:
378 : // - request.body.string *[string]*: request body storing expected string processed.
379 : // - request.body.hexstring *[string]*: request body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
380 : // - request.body.json.string *[string]*: request body document storing expected string.
381 : // - request.body.json.integer *[number]*: request body document storing expected integer.
382 : // - request.body.json.unsigned *[unsigned number]*: request body document storing expected unsigned integer.
383 : // - request.body.json.float *[float number]*: request body document storing expected float number.
384 : // - request.body.json.boolean *[boolean]*: request body document storing expected booolean.
385 : // - request.body.json.object *[json object]*: request body document storing expected object.
386 : // - request.body.json.jsonstring *[json string]*: request body document storing expected object, extracted from json-parsed string, as root node.
387 : // + request.body.json.string./<n1>/../<nN> *[string]*: request body node path storing expected string.
388 : // + request.body.json.integer./<n1>/../<nN> *[number]*: request body node path storing expected integer.
389 : // + request.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: request body node path storing expected unsigned integer.
390 : // + request.body.json.float./<n1>/../<nN> *[float number]*: request body node path storing expected float number.
391 : // + request.body.json.boolean./<n1>/../<nN> *[boolean]*: request body node path storing expected booblean.
392 : // + request.body.json.object./<n1>/../<nN> *[json object]*: request body node path storing expected object.
393 : // + request.body.json.jsonstring./<n1>/../<nN> *[json string]*: request body node path storing expected object, extracted from json-parsed string, under provided path.
394 :
395 : // Regex needed:
396 : // SERVER MODE:
397 155 : static std::regex responseBodyJson_StringNode("^response.body.json.string.(.*)", std::regex::optimize);
398 155 : static std::regex responseBodyJson_IntegerNode("^response.body.json.integer.(.*)", std::regex::optimize);
399 155 : static std::regex responseBodyJson_UnsignedNode("^response.body.json.unsigned.(.*)", std::regex::optimize);
400 155 : static std::regex responseBodyJson_FloatNode("^response.body.json.float.(.*)", std::regex::optimize);
401 155 : static std::regex responseBodyJson_BooleanNode("^response.body.json.boolean.(.*)", std::regex::optimize);
402 155 : static std::regex responseBodyJson_ObjectNode("^response.body.json.object.(.*)", std::regex::optimize);
403 155 : static std::regex responseBodyJson_JsonStringNode("^response.body.json.jsonstring.(.*)", std::regex::optimize);
404 155 : static std::regex responseHeader("^response.header.(.*)", std::regex::optimize);
405 155 : static std::regex outStateMethodUri("^outState.(POST|GET|PUT|DELETE|HEAD)(\\..+)?", std::regex::optimize);
406 :
407 : // CLIENT MODE:
408 155 : static std::regex requestBodyJson_StringNode("^request.body.json.string.(.*)", std::regex::optimize);
409 155 : static std::regex requestBodyJson_IntegerNode("^request.body.json.integer.(.*)", std::regex::optimize);
410 155 : static std::regex requestBodyJson_UnsignedNode("^request.body.json.unsigned.(.*)", std::regex::optimize);
411 155 : static std::regex requestBodyJson_FloatNode("^request.body.json.float.(.*)", std::regex::optimize);
412 155 : static std::regex requestBodyJson_BooleanNode("^request.body.json.boolean.(.*)", std::regex::optimize);
413 155 : static std::regex requestBodyJson_ObjectNode("^request.body.json.object.(.*)", std::regex::optimize);
414 155 : static std::regex requestBodyJson_JsonStringNode("^request.body.json.jsonstring.(.*)", std::regex::optimize);
415 :
416 : // Only target
417 155 : static std::regex udpSocket("^udpSocket.(.*)", std::regex::optimize);
418 :
419 : // no need to try (controlled regex)
420 : //try {
421 : // SERVER_MODE
422 155 : if (targetSpec == "response.body.string") {
423 60 : target_type_ = TargetType::ResponseBodyString;
424 : }
425 95 : else if (targetSpec == "response.body.hexstring") {
426 1 : target_type_ = TargetType::ResponseBodyHexString;
427 : }
428 94 : else if (targetSpec == "response.body.json.string") { // whole document
429 2 : target_type_ = TargetType::ResponseBodyJson_String;
430 : }
431 92 : else if (std::regex_match(targetSpec, matches, responseBodyJson_StringNode)) { // nlohmann::json_pointer path
432 15 : target_ = matches.str(1);
433 15 : target_type_ = TargetType::ResponseBodyJson_String;
434 : }
435 77 : else if (targetSpec == "response.body.json.integer") { // whole document
436 1 : target_type_ = TargetType::ResponseBodyJson_Integer;
437 : }
438 76 : else if (std::regex_match(targetSpec, matches, responseBodyJson_IntegerNode)) { // nlohmann::json_pointer path
439 5 : target_ = matches.str(1);
440 5 : target_type_ = TargetType::ResponseBodyJson_Integer;
441 : }
442 71 : else if (targetSpec == "response.body.json.unsigned") { // whole document
443 1 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
444 : }
445 70 : else if (std::regex_match(targetSpec, matches, responseBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
446 1 : target_ = matches.str(1);
447 1 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
448 : }
449 69 : else if (targetSpec == "response.body.json.float") { // whole document
450 1 : target_type_ = TargetType::ResponseBodyJson_Float;
451 : }
452 68 : else if (std::regex_match(targetSpec, matches, responseBodyJson_FloatNode)) { // nlohmann::json_pointer path
453 1 : target_ = matches.str(1);
454 1 : target_type_ = TargetType::ResponseBodyJson_Float;
455 : }
456 67 : else if (targetSpec == "response.body.json.boolean") { // whole document
457 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
458 : }
459 66 : else if (std::regex_match(targetSpec, matches, responseBodyJson_BooleanNode)) { // nlohmann::json_pointer path
460 1 : target_ = matches.str(1);
461 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
462 : }
463 65 : else if (targetSpec == "response.body.json.object") { // whole document
464 7 : target_type_ = TargetType::ResponseBodyJson_Object;
465 : }
466 58 : else if (std::regex_match(targetSpec, matches, responseBodyJson_ObjectNode)) { // nlohmann::json_pointer path
467 15 : target_ = matches.str(1);
468 15 : target_type_ = TargetType::ResponseBodyJson_Object;
469 : }
470 43 : else if (targetSpec == "response.body.json.jsonstring") { // whole document
471 2 : target_type_ = TargetType::ResponseBodyJson_JsonString;
472 : }
473 41 : else if (std::regex_match(targetSpec, matches, responseBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
474 1 : target_ = matches.str(1);
475 1 : target_type_ = TargetType::ResponseBodyJson_JsonString;
476 : }
477 : // CLIENT MODE
478 40 : else if (targetSpec == "request.body.string") {
479 0 : target_type_ = TargetType::RequestBodyString;
480 : }
481 40 : else if (targetSpec == "request.body.hexstring") {
482 0 : target_type_ = TargetType::RequestBodyHexString;
483 : }
484 40 : else if (targetSpec == "request.body.json.string") { // whole document
485 0 : target_type_ = TargetType::RequestBodyJson_String;
486 : }
487 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_StringNode)) { // nlohmann::json_pointer path
488 0 : target_ = matches.str(1);
489 0 : target_type_ = TargetType::RequestBodyJson_String;
490 : }
491 40 : else if (targetSpec == "request.body.json.integer") { // whole document
492 0 : target_type_ = TargetType::RequestBodyJson_Integer;
493 : }
494 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_IntegerNode)) { // nlohmann::json_pointer path
495 0 : target_ = matches.str(1);
496 0 : target_type_ = TargetType::RequestBodyJson_Integer;
497 : }
498 40 : else if (targetSpec == "request.body.json.unsigned") { // whole document
499 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
500 : }
501 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
502 0 : target_ = matches.str(1);
503 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
504 : }
505 40 : else if (targetSpec == "request.body.json.float") { // whole document
506 0 : target_type_ = TargetType::RequestBodyJson_Float;
507 : }
508 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_FloatNode)) { // nlohmann::json_pointer path
509 0 : target_ = matches.str(1);
510 0 : target_type_ = TargetType::RequestBodyJson_Float;
511 : }
512 40 : else if (targetSpec == "request.body.json.boolean") { // whole document
513 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
514 : }
515 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_BooleanNode)) { // nlohmann::json_pointer path
516 0 : target_ = matches.str(1);
517 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
518 : }
519 40 : else if (targetSpec == "request.body.json.object") { // whole document
520 0 : target_type_ = TargetType::RequestBodyJson_Object;
521 : }
522 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_ObjectNode)) { // nlohmann::json_pointer path
523 0 : target_ = matches.str(1);
524 0 : target_type_ = TargetType::RequestBodyJson_Object;
525 : }
526 40 : else if (targetSpec == "request.body.json.jsonstring") { // whole document
527 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
528 : }
529 40 : else if (std::regex_match(targetSpec, matches, requestBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
530 0 : target_ = matches.str(1);
531 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
532 : }
533 :
534 40 : else if (std::regex_match(targetSpec, matches, responseHeader)) { // header name
535 1 : target_ = matches.str(1);
536 1 : target_type_ = TargetType::ResponseHeader;
537 : }
538 39 : else if (targetSpec == "response.statusCode") {
539 6 : target_type_ = TargetType::ResponseStatusCode;
540 : }
541 33 : else if (targetSpec == "response.delayMs") {
542 1 : target_type_ = TargetType::ResponseDelayMs;
543 : }
544 32 : else if (std::regex_match(targetSpec, matches, varId)) { // variable id
545 10 : target_ = matches.str(1);
546 10 : target_type_ = TargetType::TVar;
547 : }
548 22 : else if (std::regex_match(targetSpec, matches, gvarId)) { // global variable id
549 4 : target_ = matches.str(1);
550 4 : target_type_ = TargetType::TGVar;
551 : }
552 18 : else if (targetSpec == "outState") {
553 1 : target_type_ = TargetType::OutState;
554 : }
555 17 : else if (std::regex_match(targetSpec, matches, outStateMethodUri)) { // method
556 6 : target_ = matches.str(1); // <method>
557 6 : target2_ = matches.str(2); // .<uri>
558 6 : if (!target2_.empty()) {
559 6 : target2_ = target2_.substr(1); // remove the initial dot to store the uri
560 : }
561 6 : target_type_ = TargetType::OutState;
562 : }
563 11 : else if (std::regex_match(targetSpec, matches, txtFile)) { // path file
564 3 : target_ = matches.str(1);
565 3 : target_type_ = TargetType::TTxtFile;
566 : }
567 8 : else if (std::regex_match(targetSpec, matches, binFile)) { // path file
568 2 : target_ = matches.str(1);
569 2 : target_type_ = TargetType::TBinFile;
570 : }
571 6 : else if (std::regex_match(targetSpec, matches, udpSocket)) { // path file
572 1 : target_ = matches.str(1);
573 1 : target_type_ = TargetType::UDPSocket;
574 : }
575 5 : else if (std::regex_match(targetSpec, matches, serverEvent)) { // value content
576 3 : target_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3
577 3 : target_type_ = TargetType::ServerEventToPurge;
578 3 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(target_);
579 3 : std::map<std::string, std::string>::const_iterator it;
580 12 : for (auto qp: {
581 : "requestMethod", "requestUri", "eventNumber"
582 15 : }) { // tokenized vector order
583 9 : it = qmap.find(qp);
584 11 : target_tokenized_.push_back((it != qmap.end()) ? it->second:"");
585 : }
586 3 : }
587 2 : else if (targetSpec == "break") {
588 2 : target_type_ = TargetType::Break;
589 : }
590 : // PROTECTED BY SCHEMA:
591 : //else { // very strange to reach this:
592 : // ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify target type for: %s", targetSpec.c_str()), ERT_FILE_LOCATION);
593 : // return false;
594 : //}
595 : //}
596 : //catch (std::regex_error &e) {
597 : // ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
598 : // return false;
599 : //}
600 :
601 : //LOGDEBUG(ert::tracing::Logger::debug(asString(), ERT_FILE_LOCATION));
602 :
603 : // Variable patterns:
604 155 : collectVariablePatterns(source_, source_patterns_);
605 155 : if (collectFilterPatterns) collectVariablePatterns(filter_, filter_patterns_); // protected to avoid possible gathering of false patterns (i.e. complex regexp's)
606 155 : collectVariablePatterns(target_, target_patterns_);
607 155 : collectVariablePatterns(target2_, target2_patterns_);
608 :
609 155 : return true;
610 157 : }
611 :
612 11 : std::string Transformation::asString() const {
613 :
614 11 : std::stringstream ss;
615 :
616 :
617 : // SOURCE
618 11 : ss << "SourceType: " << SourceTypeAsText(source_type_);
619 11 : if (source_type_ != SourceType::RequestUri && source_type_ != SourceType::RequestUriPath && source_type_ != SourceType::Eraser && source_type_ != SourceType::Recvseq && source_type_ != SourceType::InState) {
620 9 : ss << " | source_: " << source_;
621 :
622 9 : if (source_type_ == SourceType::RequestBody || source_type_ == SourceType::ResponseBody) {
623 1 : ss << " (empty: whole, path: node)";
624 : }
625 8 : else if (source_type_ == SourceType::Random) {
626 1 : ss << " | source_i1_: " << source_i1_ << " (Random min)" << " | source_i2_: " << source_i2_ << " (Random max)";
627 : }
628 7 : else if (source_type_ == SourceType::RandomSet || source_type_ == SourceType::ServerEvent) {
629 0 : ss << " | source_tokenized_:";
630 0 : for(auto it: source_tokenized_) {
631 0 : ss << " '" << it << "'";
632 0 : }
633 0 : }
634 7 : else if (source_type_ == SourceType::STxtFile || source_type_ == SourceType::SBinFile) {
635 1 : ss << " (path file)";
636 : }
637 6 : else if (source_type_ == SourceType::Command) {
638 1 : ss << " (shell command expression)";
639 : }
640 :
641 9 : if (!source_patterns_.empty()) {
642 1 : ss << " | source variables:";
643 2 : for (auto it = source_patterns_.begin(); it != source_patterns_.end(); it ++) {
644 1 : ss << " " << it->second;
645 : }
646 : }
647 : }
648 :
649 : // TARGET
650 11 : ss << " | TargetType: " << TargetTypeAsText(target_type_);
651 11 : if (target_type_ != TargetType::ResponseStatusCode &&
652 11 : target_type_ != TargetType::ResponseDelayMs &&
653 11 : target_type_ != TargetType::ResponseBodyString &&
654 4 : target_type_ != TargetType::ResponseBodyHexString ) {
655 :
656 4 : ss << " | target_: " << target_;
657 :
658 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) {
659 1 : ss << " (empty: whole, path: node)";
660 : }
661 3 : else if (target_type_ == TargetType::OutState) {
662 1 : ss << " (empty: current method, method: another)" << " | target2_: " << target2_ << "(empty: current uri, uri: another)";
663 1 : if (!target2_patterns_.empty()) {
664 1 : ss << " | target2 variables:";
665 2 : for (auto it = target2_patterns_.begin(); it != target2_patterns_.end(); it ++) {
666 1 : ss << " " << it->second;
667 : }
668 : }
669 : }
670 2 : else if (target_type_ == TargetType::TTxtFile || target_type_ == TargetType::TBinFile) {
671 1 : ss << " (path file)";
672 : }
673 1 : else if (target_type_ == TargetType::UDPSocket) {
674 0 : ss << " (<path file>[|<write delay ms>])";
675 : }
676 :
677 4 : if (!target_patterns_.empty()) {
678 1 : ss << " | target variables:";
679 2 : for (auto it = target_patterns_.begin(); it != target_patterns_.end(); it ++) {
680 1 : ss << " " << it->second;
681 : }
682 : }
683 : }
684 :
685 : // FILTER
686 11 : if (has_filter_) {
687 4 : ss << " | FilterType: " << FilterTypeAsText(filter_type_);
688 4 : if (filter_type_ != FilterType::Sum && filter_type_ != FilterType::Multiply) {
689 : /*<< " | filter_rgx_: ?"*/
690 3 : ss << " | filter_ " << filter_;
691 :
692 3 : if (filter_type_ == FilterType::RegexReplace) {
693 1 : ss << " (fmt)";
694 : }
695 2 : else if (filter_type_ == FilterType::RegexCapture) {
696 1 : ss << " (literal, although not actually needed, but useful to access & print on traces)";
697 : }
698 : }
699 : else {
700 1 : ss << " | filter_number_type_: " << filter_number_type_ << " (0: integer, 1: unsigned, 2: float)"
701 1 : << " | filter_i_: " << filter_i_ << " | filter_u_: " << filter_u_ << " | filter_f_: " << filter_f_;
702 : }
703 :
704 4 : if (!filter_patterns_.empty()) {
705 1 : ss << " | filter variables:";
706 2 : for (auto it = filter_patterns_.begin(); it != filter_patterns_.end(); it ++) {
707 1 : ss << " " << it->second;
708 : }
709 : }
710 : }
711 :
712 22 : return ss.str();
713 11 : }
714 :
715 :
716 : }
717 : }
718 :
|