1 /*
2     Copyright (C) 2013 Andrew Caudwell (acaudwell@gmail.com)
3 
4     This program is free software; you can redistribute it and/or
5     modify it under the terms of the GNU General Public License
6     as published by the Free Software Foundation; either version
7     3 of the License, or (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include "settings.h"
19 
20 #include "core/logger.h"
21 #include "core/sdlapp.h"
22 #include "core/seeklog.h"
23 #include "logentry.h"
24 
25 #include <time.h>
26 #include <algorithm>
27 #include <boost/algorithm/string.hpp>
28 
29 LogstalgiaSettings settings;
30 
31 //display help message
help(bool extended_help)32 void LogstalgiaSettings::help(bool extended_help) {
33 
34 #ifdef _WIN32
35     //resize window to fit help message
36     SDLApp::resizeConsole(920);
37     SDLApp::showConsole(true);
38 #endif
39 
40     printf("Logstalgia v%s\n", LOGSTALGIA_VERSION);
41 
42     printf("Usage: logstalgia [options] logfile\n\n");
43     printf("Options:\n");
44     printf("  -WIDTHxHEIGHT              Set window size\n");
45     printf("  -f, --fullscreen           Fullscreen\n");
46     printf("  --screen SCREEN            Screen number\n");
47     printf("  --window-position XxY      Initial window position\n");
48     printf("  --frameless                Frameless window\n");
49     printf("  --title TITLE              Set a title\n\n");
50 
51     printf("  -b --background FFFFFF     Background colour in hex\n\n");
52 
53     printf("  -x --full-hostnames        Show full request ip/hostname\n");
54     printf("  -s --simulation-speed      Simulation speed (default: 1)\n");
55     printf("  -p --pitch-speed           Speed balls travel across screen (default: 0.15)\n");
56     printf("  -u --update-rate           Page summary update rate (default: 5)\n\n");
57 
58     printf("  -g name,(HOST|URI|CODE)=regex[,SEP=chars][,MAX=n][,ABBR=n],percent[,colour]\n");
59     printf("                             Group together requests where the HOST, URI\n");
60     printf("                             or response CODE matches a regular expression\n\n");
61 
62     printf("  --address-separators CHARS List of address separator characters\n");
63     printf("  --address-max-depth DEPTH  Maximum depth to display in address summarizer\n");
64     printf("  --address-abbr-depth DEPTH Minimum abbreviation depth of address summarizer\n\n");
65 
66     printf("  --path-separators CHARS   Default list of path separator characters\n");
67     printf("  --path-max-depth DEPTH    Default maximum path depth\n");
68     printf("  --path-abbr-depth DEPTH   Default minimum path abbreviation depth\n\n");
69 
70     printf("  --paddle-mode MODE         Paddle mode (single, pid, vhost)\n");
71     printf("  --paddle-position POSITION Paddle position as a fraction of the view width\n\n");
72 
73     printf("  --display-fields FIELDS    Comma separated list of fields shown on hover:\n");
74     printf("                             timestamp,hostname,path,method,protocol\n");
75     printf("                             response_size,response_code,referrer\n");
76     printf("                             user_agent,vhost,pid,log_entry\n\n");
77 
78     printf("  --sync                     Read from STDIN, ignoring entries before now\n\n");
79 
80     printf("  --from, --to 'YYYY-MM-DD hh:mm:ss'  Show entries from a specific time period\n\n");
81 
82     printf("  --start-position POSITION  Begin at some position in the log (0.0 - 1.0)\n");
83     printf("  --stop-position  POSITION  Stop at some position\n\n");
84 
85     printf("  --no-bounce                No bouncing\n\n");
86 
87     printf("  --hide-response-code       Hide response code\n");
88     printf("  --hide-paddle              Hide paddle\n");
89     printf("  --hide-paddle-tokens       Hide paddle tokens shown in multi-paddle modes\n");
90     printf("  --hide-url-prefix          Hide URL protocol and hostname prefix\n\n");
91 
92     printf("  --disable-auto-skip        Disable skipping of empty time periods\n");
93     printf("  --disable-progress         Disable the progress bar\n");
94     printf("  --disable-glow             Disable the glow effect\n\n");
95 
96     printf("  --font-size SIZE           Font size\n\n");
97 
98     printf("  --glow-duration            Duration of the glow (default: 0.15)\n");
99     printf("  --glow-multiplier          Adjust the amount of glow (default: 1.25)\n");
100     printf("  --glow-intensity           Intensity of the glow (default: 0.5)\n\n");
101 
102     printf("  --load-config CONF_FILE    Load a config file\n");
103     printf("  --save-config CONF_FILE    Save a config file with the current options\n");
104     printf("  --detect-changes           Automatically reload modified config file\n\n");
105 
106     printf("  -o, --output-ppm-stream FILE   Write frames as PPM to a file ('-' for STDOUT)\n");
107     printf("  -r, --output-framerate  FPS    Framerate of output (25,30,60)\n\n");
108 
109     printf("FILE should be a log file or '-' to read STDIN.\n\n");
110 
111     if(extended_help) {
112     }
113 
114 #ifdef _WIN32
115     if(!SDLApp::existing_console) {
116         printf("Press Enter\n");
117         getchar();
118     }
119 #endif
120 
121     exit(0);
122 }
123 
LogstalgiaSettings()124 LogstalgiaSettings::LogstalgiaSettings() {
125     log_level = LOG_LEVEL_OFF;
126     splash    = -1.0f;
127     run_tests = false;
128 
129     setLogstalgiaDefaults();
130 
131     default_section_name = "logstalgia";
132 
133     //translate args
134     arg_aliases["g"] = "group";
135     arg_aliases["p"] = "start-position";
136     arg_aliases["b"] = "background";
137     arg_aliases["x"] = "full-hostnames";
138     arg_aliases["u"] = "update-rate";
139     arg_aliases["s"] = "simulation-speed";
140     arg_aliases["speed"] = "simulation-speed";
141     arg_aliases["p"] = "pitch-speed";
142     arg_aliases["c"] = "splash";
143     arg_aliases["H"] = "extended-help";
144     arg_aliases["h"] = "help";
145     arg_aliases["?"] = "help";
146 
147     //command line only options
148     conf_sections["help"]            = "command-line";
149     conf_sections["test"]            = "command-line";
150     conf_sections["extended-help"]   = "command-line";
151     conf_sections["load-config"]     = "command-line";
152     conf_sections["save-config"]     = "command-line";
153     conf_sections["log-level"]       = "command-line";
154     conf_sections["splash"]          = "command-line";
155 
156     // arg types
157 
158     arg_types["font-size"]          = "int";
159     arg_types["address-max-depth"]  = "int";
160     arg_types["address-abbr-depth"] = "int";
161     arg_types["path-max-depth"]     = "int";
162     arg_types["path-abbr-depth"]    = "int";
163 
164     arg_types["help"]          = "bool";
165     arg_types["test"]          = "bool";
166     arg_types["extended-help"] = "bool";
167     arg_types["splash"]        = "bool";
168 
169     arg_types["sync"]            = "bool";
170     arg_types["full-hostnames"]  = "bool";
171     arg_types["no-bounce"]       = "bool";
172     arg_types["detect-changes"]  = "bool";
173     arg_types["ffp"]             = "bool";
174 
175     arg_types["hide-paddle"]        = "bool";
176     arg_types["hide-paddle-tokens"] = "bool";
177     arg_types["hide-response-code"] = "bool";
178     arg_types["hide-url-prefix"]    = "bool";
179 
180     arg_types["disable-auto-skip"] = "bool";
181     arg_types["disable-progress"]  = "bool";
182     arg_types["disable-glow"]      = "bool";
183 
184     arg_types["glow-intensity"]   = "float";
185     arg_types["glow-multiplier"]  = "float";
186     arg_types["glow-duration"]    = "float";
187     arg_types["paddle-position"]  = "float";
188 
189     arg_types["pitch-speed"]      = "float";
190     arg_types["simulation-speed"] = "float";
191     arg_types["update-rate"]      = "float";
192 
193     arg_types["group"] = "multi-value";
194 
195     arg_types["to"]                 = "string";
196     arg_types["from"]               = "string";
197     arg_types["log-level"]          = "string";
198     arg_types["load-config"]        = "string";
199     arg_types["save-config"]        = "string";
200     arg_types["path"]               = "string";
201     arg_types["background"]         = "string";
202     arg_types["start-position"]     = "string";
203     arg_types["stop-position"]      = "string";
204     arg_types["paddle-mode"]        = "string";
205     arg_types["display-fields"]     = "string";
206     arg_types["address-separators"] = "string";
207     arg_types["group-separators"]   = "string";
208 
209     arg_types["title"] = "string";
210 }
211 
setLogstalgiaDefaults()212 void LogstalgiaSettings::setLogstalgiaDefaults() {
213 
214     path = "";
215     display_fields.clear();
216     display_log_entry = false;
217 
218     sync = false;
219 
220     start_time = stop_time = 0;
221 
222     start_position = 0.0f;
223     stop_position  = 1.0f;
224 
225     address_max_depth  = 0;
226     address_abbr_depth = 0;
227     address_separators = ".:";
228 
229     path_max_depth  = 0;
230     path_abbr_depth = 0;
231     path_separators = "/";
232 
233     detect_changes = false;
234 
235     paddle_mode     = PADDLE_SINGLE;
236     paddle_position = 0.67f;
237 
238     pitch_speed       = 0.15f;
239     simulation_speed  = 1.0f;
240     update_rate       = 5.0f;
241 
242     glow_intensity  = 0.5f;
243     glow_multiplier = 1.25f;
244     glow_duration   = 0.15f;
245 
246     disable_auto_skip  = false;
247     disable_progress   = false;
248     disable_glow       = false;
249 
250     hide_response_code = false;
251     hide_paddle        = false;
252     hide_url_prefix    = false;
253     hide_paddle_tokens = false;
254 
255     no_bounce          = false;
256 
257     mask_hostnames = true;
258 
259     ffp = false;
260 
261     background_colour = vec3(0.0f, 0.0f, 0.0f);
262 
263     font_size = 14;
264 
265     title = "";
266 
267     groups.clear();
268 }
269 
commandLineOption(const std::string & name,const std::string & value)270 void LogstalgiaSettings::commandLineOption(const std::string& name, const std::string& value) {
271 
272     if(name == "help") {
273         help();
274     }
275 
276     if(name == "test") {
277         run_tests = true;
278         return;
279     }
280 
281     if(name == "extended-help") {
282         help(true);
283     }
284 
285     if(name == "splash") {
286         splash = 10.0f;
287         return;
288     }
289 
290     if(name == "load-config" && value.size() > 0) {
291         load_config = value;
292         return;
293     }
294 
295     if(name == "save-config" && value.size() > 0) {
296         save_config = value;
297         return;
298     }
299 
300     if(name == "log-level") {
301         if(value == "warn") {
302             log_level = LOG_LEVEL_WARN;
303         } else if(value == "debug") {
304             log_level = LOG_LEVEL_DEBUG;
305         } else if(value == "info") {
306             log_level = LOG_LEVEL_INFO;
307         } else if(value == "error") {
308             log_level = LOG_LEVEL_ERROR;
309         } else if(value == "pedantic") {
310             log_level = LOG_LEVEL_PEDANTIC;
311         }
312         return;
313     }
314 
315     std::string invalid_error = std::string("invalid ") + name + std::string(" value");
316     throw ConfFileException(invalid_error, "", 0);
317 }
318 
importLogstalgiaSettings(ConfFile & conffile,ConfSection * settings)319 void LogstalgiaSettings::importLogstalgiaSettings(ConfFile& conffile, ConfSection* settings) {
320 
321     setLogstalgiaDefaults();
322 
323     if(settings == 0) settings = conffile.getSection(default_section_name);
324 
325     if(settings == 0) {
326         settings = conffile.addSection("logstalgia");
327     }
328 
329     ConfEntry* entry = 0;
330 
331     if((entry = settings->getEntry("glow-intensity")) != 0) {
332 
333         if(!entry->hasValue()) conffile.entryException(entry, "specify glow-intensity (float)");
334 
335         glow_intensity = entry->getFloat();
336 
337         if(glow_intensity <= 0.0f) {
338             conffile.invalidValueException(entry);
339         }
340     }
341 
342     if((entry = settings->getEntry("glow-multiplier")) != 0) {
343 
344         if(!entry->hasValue()) conffile.entryException(entry, "specify glow-multiplier (float)");
345 
346         glow_multiplier = entry->getFloat();
347 
348         if(glow_multiplier <= 0.0f) {
349             conffile.invalidValueException(entry);
350         }
351     }
352 
353     if((entry = settings->getEntry("glow-duration")) != 0) {
354 
355         if(!entry->hasValue()) conffile.entryException(entry, "specify glow-duration (float)");
356 
357         glow_duration = entry->getFloat();
358 
359         if(glow_duration <= 0.0f || glow_duration > 1.0f) {
360             conffile.invalidValueException(entry);
361         }
362     }
363 
364     if((entry = settings->getEntry("font-size")) != 0) {
365 
366         if(!entry->hasValue()) conffile.entryException(entry, "specify font size");
367 
368         font_size = entry->getInt();
369 
370         if(font_size<1 || font_size>100) {
371             conffile.invalidValueException(entry);
372         }
373     }
374 
375     if((entry = settings->getEntry("address-separators")) != 0) {
376 
377         if(!entry->hasValue()) conffile.entryException(entry, "specify address separators");
378 
379         address_separators = entry->getString();
380 
381         if(address_separators.empty() || address_separators.size() > 100) {
382             conffile.invalidValueException(entry);
383         }
384     }
385 
386     if((entry = settings->getEntry("address-max-depth")) != 0) {
387 
388         if(!entry->hasValue()) conffile.entryException(entry, "specify address max depth");
389 
390         address_max_depth = entry->getInt();
391 
392         if(address_max_depth < 0) {
393             conffile.invalidValueException(entry);
394         }
395     }
396 
397     if((entry = settings->getEntry("address-abbr-depth")) != 0) {
398 
399         if(!entry->hasValue()) conffile.entryException(entry, "specify address abbreviation depth");
400 
401         address_abbr_depth = entry->getInt();
402 
403         if(address_abbr_depth < -1) {
404             conffile.invalidValueException(entry);
405         }
406     }
407 
408     if((entry = settings->getEntry("path-separators")) != 0) {
409 
410         if(!entry->hasValue()) conffile.entryException(entry, "specify path separators");
411 
412         path_separators = entry->getString();
413 
414         if(path_separators.empty() || path_separators.size() > 100) {
415             conffile.invalidValueException(entry);
416         }
417     }
418 
419     if((entry = settings->getEntry("path-max-depth")) != 0) {
420 
421         if(!entry->hasValue()) conffile.entryException(entry, "specify path max depth");
422 
423         path_max_depth = entry->getInt();
424 
425         if(path_max_depth < 0) {
426             conffile.invalidValueException(entry);
427         }
428     }
429 
430     if((entry = settings->getEntry("path-abbr-depth")) != 0) {
431 
432         if(!entry->hasValue()) conffile.entryException(entry, "specify path abbreviation depth");
433 
434         path_abbr_depth = entry->getInt();
435 
436         if(path_abbr_depth < -1) {
437             conffile.invalidValueException(entry);
438         }
439     }
440 
441     if((entry = settings->getEntry("background")) != 0) {
442 
443         if(!entry->hasValue()) conffile.entryException(entry, "specify background colour (FFFFFF)");
444 
445         int r,g,b;
446 
447         std::string colstring = entry->getString();
448 
449         if(entry->isVec3()) {
450             background_colour = entry->getVec3();
451         } else if(colstring.size()==6 && sscanf(colstring.c_str(), "%02x%02x%02x", &r, &g, &b) == 3) {
452             background_colour = vec3(r,g,b);
453             background_colour /= 255.0f;
454         } else {
455             conffile.invalidValueException(entry);
456         }
457     }
458 
459     if((entry = settings->getEntry("from")) != 0) {
460 
461         if(!entry->hasValue()) conffile.entryException(entry, "specify from (YYYY-MM-DD hh:mm:ss)");
462 
463         if(!parseDateTime(entry->getString(), start_time)) {
464             conffile.invalidValueException(entry);
465         }
466     }
467 
468     if((entry = settings->getEntry("to")) != 0) {
469 
470         if(!entry->hasValue()) conffile.entryException(entry, "specify to (YYYY-MM-DD hh:mm:ss)");
471 
472         if(!parseDateTime(entry->getString(), stop_time)) {
473             conffile.invalidValueException(entry);
474         }
475     }
476 
477     if((entry = settings->getEntry("start-position")) != 0) {
478 
479         if(!entry->hasValue()) conffile.entryException(entry, "specify start-position (float,random)");
480 
481         start_position = entry->getFloat();
482 
483         if(start_position<=0.0 || start_position>=1.0) {
484             conffile.entryException(entry, "start-position outside of range 0.0 - 1.0 (non-inclusive)");
485         }
486     }
487 
488     if((entry = settings->getEntry("stop-position")) != 0) {
489 
490         if(!entry->hasValue()) conffile.entryException(entry, "specify stop-position (float)");
491 
492         stop_position = entry->getFloat();
493 
494         if(stop_position<=0.0 || stop_position>1.0) {
495             conffile.entryException(entry, "stop-position outside of range 0.0 - 1.0 (inclusive)");
496         }
497     }
498 
499     if((entry = settings->getEntry("group")) != 0) {
500 
501         ConfEntryList* group_entries = settings->getEntries("group");
502 
503         for(ConfEntry* entry : *group_entries) {
504             if(!entry->hasValue()) conffile.entryException(entry, "specify group definition");
505 
506             SummarizerGroup group;
507             std::string error;
508 
509             if(!SummarizerGroup::parse(entry->getString(), group, error)) {
510                 if(error.empty()) error = "invalid group definition";
511                 conffile.entryException(entry, error);
512             }
513 
514             groups.push_back(group);
515         }
516     }
517 
518     if((entry = settings->getEntry("paddle-mode")) != 0) {
519 
520         if(!entry->hasValue()) conffile.entryException(entry, "specify paddle-mode (vhost,pid)");
521 
522         std::string paddle_mode_string = entry->getString();
523 
524         if(paddle_mode_string == "single") {
525             paddle_mode = PADDLE_SINGLE;
526 
527         } else if(paddle_mode_string == "pid") {
528             paddle_mode = PADDLE_PID;
529 
530         } else if(paddle_mode_string == "vhost") {
531             paddle_mode = PADDLE_VHOST;
532 
533         } else {
534             conffile.entryException(entry, "invalid paddle-mode");
535         }
536     }
537 
538     if((entry = settings->getEntry("paddle-position")) != 0) {
539 
540         if(!entry->hasValue()) conffile.entryException(entry, "specify paddle-position (0.25 - 0.75)");
541 
542         paddle_position = entry->getFloat();
543 
544         if(paddle_position < 0.25f || paddle_position > 0.75f) {
545             conffile.entryException(entry, "paddle-position outside of range 0.25 - 0.75");
546         }
547     }
548 
549     if((entry = settings->getEntry("pitch-speed")) != 0) {
550 
551         if(!entry->hasValue()) conffile.entryException(entry, "specify pitch speed (0.1 to 10.0)");
552 
553         pitch_speed = entry->getFloat();
554 
555         if(pitch_speed < 0.1f || pitch_speed > 10.0f) {
556             conffile.entryException(entry, "pitch speed should be between 0.1 and 10.0");
557         }
558     }
559 
560     if((entry = settings->getEntry("simulation-speed")) != 0) {
561 
562         if(!entry->hasValue()) conffile.entryException(entry, "specify simulation speed (0.1 to 30)");
563 
564         simulation_speed = entry->getFloat();
565 
566         if(simulation_speed < 0.1f || simulation_speed > 30.0f) {
567             conffile.entryException(entry, "simulation speed should be between 0.1 and 30");
568         }
569     }
570 
571     if((entry = settings->getEntry("update-rate")) != 0) {
572 
573         if(!entry->hasValue()) conffile.entryException(entry, "specify update rate (1 to 60)");
574 
575         update_rate = entry->getFloat();
576 
577         if(update_rate < 1.0f || update_rate > 60.0f) {
578             conffile.entryException(entry, "update rate should be between 1 and 60");
579         }
580     }
581 
582     if(settings->getBool("sync")) {
583         sync = true;
584     }
585 
586     if(settings->getBool("hide-paddle")) {
587         paddle_mode = PADDLE_NONE;
588     }
589 
590     if(settings->getBool("hide-paddle-tokens")) {
591         hide_paddle_tokens = true;
592     }
593 
594     if(settings->getBool("hide-response-code")) {
595         hide_response_code = true;
596     }
597 
598     if(settings->getBool("no-bounce")) {
599         no_bounce = true;
600     }
601 
602     if(settings->getBool("disable-auto-skip")) {
603         disable_auto_skip = true;
604     }
605 
606     if(settings->getBool("disable-progress")) {
607         disable_progress = true;
608     }
609 
610     if(settings->getBool("disable-glow")) {
611         disable_glow = true;
612     }
613 
614     if(settings->getBool("full-hostnames")) {
615         mask_hostnames = false;
616     }
617 
618     if(settings->getBool("hide-url-prefix")) {
619         hide_url_prefix = true;
620     }
621 
622     if(settings->getBool("ffp")) {
623         ffp = true;
624     }
625 
626     if(settings->getBool("detect-changes")) {
627         detect_changes = true;
628     }
629 
630     if((entry = settings->getEntry("display-fields")) != 0) {
631         display_fields.clear();
632         display_log_entry = false;
633 
634         if(!entry->hasValue()) conffile.missingValueException(entry);
635 
636         std::string field_list = entry->getString();
637 
638         boost::algorithm::erase_all(field_list, " ");
639 
640         size_t sep;
641         while((sep = field_list.find(",")) != std::string::npos) {
642 
643             if(sep == 0 && field_list.size()==1) break;
644 
645             if(sep == 0) {
646                 field_list = field_list.substr(sep+1, field_list.size()-1);
647                 continue;
648             }
649 
650             std::string field = field_list.substr(0, sep);
651             display_fields.push_back(field);
652             field_list = field_list.substr(sep+1, field_list.size()-1);
653         }
654 
655         if(field_list.size() > 0 && field_list != ",") {
656             display_fields.push_back(field_list);
657         }
658 
659         const std::vector<std::string>& valid_fields = LogEntry::getFields();
660 
661         for(const std::string& field : display_fields) {
662             if(std::find(valid_fields.begin(), valid_fields.end(), field) == valid_fields.end()) {
663                 std::string invalid_field_error = std::string("invalid display field ") + field;
664                 conffile.entryException(entry, invalid_field_error);
665             }
666 
667             if(field == "log_entry") {
668                 display_log_entry = true;
669             }
670         }
671     }
672 
673     //validate path
674     if(settings->hasValue("path")) {
675         path = settings->getString("path");
676     }
677 
678     if (path.empty() && !isatty(fileno(stdin))) {
679         path = "-";
680     }
681 
682     if(path == "-") {
683         /*
684         if(log_format.size() == 0) {
685             throw ConfFileException("log-format required when reading from STDIN", "", 0);
686         }*/
687 
688 #ifdef _WIN32
689         DWORD available_bytes;
690         HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
691 
692         while(PeekNamedPipe(stdin_handle, 0, 0, 0,
693             &available_bytes, 0) && available_bytes==0 && !std::cin.fail()) {
694             SDL_Delay(100);
695         }
696 #else
697         while(std::cin.peek() == EOF && !std::cin.fail()) SDL_Delay(100);
698 #endif
699 
700         std::cin.clear();
701     }
702 
703     if((entry = settings->getEntry("title")) != 0) {
704 
705         if(!entry->hasValue()) conffile.entryException(entry, "specify title");
706 
707         title = entry->getString();
708     }
709 
710 }
711 
exportLogstalgiaSettings(ConfFile & conf)712 void LogstalgiaSettings::exportLogstalgiaSettings(ConfFile& conf) {
713 
714     ConfSection* settings = conf.addSection("logstalgia");
715 
716     settings->addEntry(new ConfEntry("glow-intensity",  glow_intensity));
717     settings->addEntry(new ConfEntry("glow-multiplier", glow_multiplier));
718     settings->addEntry(new ConfEntry("glow-duration",   glow_duration));
719     settings->addEntry(new ConfEntry("font-size",       font_size));
720 
721     if(address_separators != ".:") {
722         settings->addEntry(new ConfEntry("address-separators", address_separators));
723     }
724 
725     if(address_max_depth != 0) {
726         settings->addEntry(new ConfEntry("address-max-depth", address_max_depth));
727     }
728 
729     if(address_abbr_depth != 0) {
730         settings->addEntry(new ConfEntry("address-abbr-depth", address_abbr_depth));
731     }
732 
733     if(path_separators != "/") {
734         settings->addEntry(new ConfEntry("path-separators", path_separators));
735     }
736 
737     if(path_max_depth != 0) {
738         settings->addEntry(new ConfEntry("group-max-depth", path_max_depth));
739     }
740 
741     if(path_abbr_depth != 0) {
742         settings->addEntry(new ConfEntry("group-abbr-depth", path_abbr_depth));
743     }
744 
745     if(!display_fields.empty()) {
746 
747         std::string display_fields_string;
748 
749         for(const std::string& field : display_fields) {
750             if(!display_fields_string.empty()) display_fields_string += ",";
751             display_fields_string += field;
752         }
753 
754         settings->addEntry(new ConfEntry("display-fields", display_fields_string));
755     }
756 
757     if(background_colour != vec3(0.0f)) {
758         char background_hex[256];
759         vec3 bg = background_colour * 255.0f;
760         snprintf(background_hex, 256, "%02X%02X%02X", (int)bg.x,(int)bg.y,(int)bg.z);
761         settings->addEntry(new ConfEntry("background", std::string(background_hex)));
762     }
763 
764     if(start_time != 0) {
765         char timestr[256];
766         struct tm* timeinfo = localtime ( &start_time );
767         strftime(timestr, 256, "%s", timeinfo);
768         settings->addEntry(new ConfEntry("from", std::string(timestr)));
769     }
770 
771     if(stop_time != 0) {
772         char timestr[256];
773         struct tm* timeinfo = localtime ( &stop_time );
774         strftime(timestr, 256, "%s", timeinfo);
775         settings->addEntry(new ConfEntry("to", std::string(timestr)));
776     }
777 
778     if(start_position > 0.0f) {
779         settings->addEntry(new ConfEntry("start-position", start_position));
780     }
781 
782     if(stop_position < 1.0f) {
783         settings->addEntry(new ConfEntry("start-position", stop_position));
784     }
785 
786     for(const SummarizerGroup& group : groups) {
787         settings->addEntry("group", group.definition);
788     }
789 
790     if(paddle_mode != PADDLE_NONE) {
791         std::string paddle_mode_string;
792 
793         switch(paddle_mode) {
794             case PADDLE_PID:
795                 paddle_mode_string = "pid";
796                 break;
797             case PADDLE_VHOST:
798                 paddle_mode_string = "vhost";
799                 break;
800             case PADDLE_SINGLE:
801             default:
802                 break;
803         }
804 
805         if(!paddle_mode_string.empty()) {
806             settings->addEntry(new ConfEntry("paddle-mode", paddle_mode_string));
807         }
808     } else {
809         settings->addEntry(new ConfEntry("hide-paddle", true));
810     }
811 
812     settings->addEntry(new ConfEntry("paddle-position", paddle_position));
813     settings->addEntry(new ConfEntry("pitch-speed", pitch_speed));
814 
815     if(simulation_speed != 1.0f) {
816         settings->addEntry(new ConfEntry("simulation-speed", simulation_speed));
817     }
818 
819     if(update_rate != 5.0f) {
820         settings->addEntry(new ConfEntry("update-rate", update_rate));
821     }
822 
823     if(sync) {
824         settings->addEntry(new ConfEntry("sync", sync));
825     }
826 
827     if(hide_paddle_tokens) {
828         settings->addEntry(new ConfEntry("hide-paddle-tokens", hide_paddle_tokens));
829     }
830 
831     if(hide_response_code) {
832         settings->addEntry(new ConfEntry("hide-response-code", hide_response_code));
833     }
834 
835     if(no_bounce) {
836         settings->addEntry(new ConfEntry("no-bounce", no_bounce));
837     }
838 
839     if(disable_auto_skip) {
840         settings->addEntry(new ConfEntry("disable-auto-skip", disable_auto_skip));
841     }
842 
843     if(disable_progress) {
844         settings->addEntry(new ConfEntry("disable-progress", disable_progress));
845     }
846 
847     if(disable_glow) {
848         settings->addEntry(new ConfEntry("disable-glow", disable_glow));
849     }
850 
851     if(mask_hostnames == false) {
852         settings->addEntry(new ConfEntry("full-hostnames", true));
853     }
854 
855     if(hide_url_prefix) {
856         settings->addEntry(new ConfEntry("hide-url-prefix", hide_url_prefix));
857     }
858 
859     if(ffp) {
860         settings->addEntry(new ConfEntry("ffp", ffp));
861     }
862 
863     if(detect_changes) {
864         settings->addEntry(new ConfEntry("detect-changes", true));
865     }
866 
867     settings->addEntry("path", path);
868 
869     if (title.length() > 0) {
870         settings->addEntry(new ConfEntry("title", title));
871     }
872 }
873 
874 // SummarizerGroup
875 
SummarizerGroup()876 SummarizerGroup::SummarizerGroup() {
877     max_depth = 0;
878     abbrev_depth = 0;
879     percent = 0;
880     colour = vec3(0.0f);
881 }
882 
parse(const std::string & group_string,SummarizerGroup & group,std::string & error)883 bool SummarizerGroup::parse(const std::string& group_string, SummarizerGroup& group, std::string& error) {
884 
885     std::vector<std::string> group_definition;
886     Regex groupregex("^([^,]+),(?:(HOST|CODE|URI)=)?([^,]+)(?:,SEP=([^,]+))?(?:,MAX=([^,]+))?(?:,ABBR=([^,]+))?,(\\d+)(?:,([^,]+))?$");
887     groupregex.match(group_string, &group_definition);
888 
889     /*
890     for(int i=0;i<group_definition.size();i++) {
891         debugLog("group_definition[%d] = %s", i, group_definition[i].c_str());
892     }
893     */
894 
895     // TODO: make this white?
896     vec3 colour(0.0f, 0.0f, 0.0f);
897 
898     if(group_definition.size()>=6) {
899         std::string group_name  = group_definition[0];
900         std::string group_type  = group_definition[1];
901         std::string group_regex = group_definition[2];
902         std::string separators  = group_definition[3];
903 
904         if(group_type.empty()) group_type = "URI";
905         if(separators.empty()) separators = settings.path_separators;
906 
907         int max_depth    = group_definition[4].empty() ? settings.path_max_depth  : atoi(group_definition[4].c_str());
908         int abbrev_depth = group_definition[5].empty() ? settings.path_abbr_depth : atoi(group_definition[5].c_str());
909 
910         int percent      = atoi(group_definition[6].c_str());
911 
912         debugLog("group name %s type %s regex %s max %d abbrev %d percent %d",
913                  group_name.c_str(), group_type.c_str(), group_regex.c_str(), max_depth, abbrev_depth, percent);
914 
915         // TODO: allow ommiting percent, if percent == 0, divide up remaining space amoung groups with no percent
916 
917         //check for optional colour param
918         if(group_definition.size() >= 8) {
919             int r, g, b;
920             if(sscanf(group_definition[7].c_str(), "%02x%02x%02x", &r, &g, &b) == 3) {
921                 colour = vec3( r, g, b );
922                 debugLog("r = %d, g = %d, b = %d\n", r, g, b);
923                 colour /= 255.0f;
924             }
925         }
926 
927         Regex regex(group_regex, true);
928         if(!regex.isValid()) {
929             error = "invalid regular expression '" + group_regex + "'";
930             return false;
931         }
932 
933         if(percent < 0 || percent > 100) {
934             error = "invalid percent value";
935             return false;
936         }
937 
938         if(abbrev_depth < -1) {
939             error = "invalid ABBR value";
940             return false;
941         }
942 
943         if(max_depth < 0) {
944             error = "invalid MAX value";
945             return false;
946         }
947 
948         group.title = group_name;
949         group.type = group_type;
950         group.regex = group_regex;
951         group.separators = separators;
952         group.max_depth = max_depth;
953         group.abbrev_depth = abbrev_depth;
954         group.percent = percent;
955         group.colour = colour;
956         group.definition = group_string;
957 
958         return true;
959     }
960 
961     return false;
962 }
963