FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
winpr/libwinpr/utils/cmdline.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/assert.h>
24#include <winpr/cmdline.h>
25
26#include "../log.h"
27
28#define TAG WINPR_TAG("commandline")
29
50#if !defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
51static const char censoredmessage[] =
52 "<censored: build with -DWITH_DEBUG_UTILS_CMDLINE_DUMP=ON for details>";
53#endif
54
55#define log_error(flags, msg, index, arg) \
56 log_error_((flags), (msg), (index), (arg), __FILE__, __func__, __LINE__)
57static void log_error_(DWORD flags, LPCSTR message, int index, WINPR_ATTR_UNUSED LPCSTR argv,
58 const char* file, const char* fkt, size_t line)
59{
60 if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
61 {
62 const DWORD level = WLOG_ERROR;
63 static wLog* log = NULL;
64 if (!WLog_IsLevelActive(log, level))
65 return;
66
67 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
68 "Failed at index %d [%s]: %s", index,
69#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
70 argv
71#else
72 censoredmessage
73#endif
74 ,
75 message);
76 }
77}
78
79#define log_comma_error(msg, arg) log_comma_error_((msg), (arg), __FILE__, __func__, __LINE__)
80static void log_comma_error_(const char* message, WINPR_ATTR_UNUSED const char* argument,
81 const char* file, const char* fkt, size_t line)
82{
83 const DWORD level = WLOG_ERROR;
84 static wLog* log = NULL;
85 if (!log)
86 log = WLog_Get(TAG);
87
88 if (!WLog_IsLevelActive(log, level))
89 return;
90
91 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s [%s]", message,
92#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
93 argument
94#else
95 censoredmessage
96#endif
97 );
98}
99
100int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
101 void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
102 COMMAND_LINE_POST_FILTER_FN_A postFilter)
103{
104 int status = 0;
105 int count = 0;
106 BOOL notescaped = FALSE;
107 const char* sigil = NULL;
108 size_t sigil_length = 0;
109 char* keyword = NULL;
110 size_t keyword_index = 0;
111 char* separator = NULL;
112 char* value = NULL;
113 int toggle = 0;
114
115 if (!argv)
116 return status;
117
118 if (argc == 1)
119 {
120 if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
121 status = 0;
122 else
123 status = COMMAND_LINE_STATUS_PRINT_HELP;
124
125 return status;
126 }
127
128 for (int i = 1; i < argc; i++)
129 {
130 size_t keyword_length = 0;
131 BOOL found = FALSE;
132 BOOL escaped = TRUE;
133
134 if (preFilter)
135 {
136 count = preFilter(context, i, argc, argv);
137
138 if (count < 0)
139 {
140 log_error(flags, "Failed for index %d [%s]: PreFilter rule could not be applied", i,
141 argv[i]);
142 status = COMMAND_LINE_ERROR;
143 return status;
144 }
145
146 if (count > 0)
147 {
148 i += (count - 1);
149 continue;
150 }
151 }
152
153 sigil = argv[i];
154 size_t length = strlen(argv[i]);
155
156 if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
157 {
158 sigil_length = 1;
159 }
160 else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
161 {
162 sigil_length = 1;
163
164 if (length > 2)
165 {
166 if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
167 sigil_length = 2;
168 }
169 }
170 else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
171 {
172 sigil_length = 1;
173 }
174 else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
175 {
176 sigil_length = 1;
177 }
178 else if (flags & COMMAND_LINE_SIGIL_NONE)
179 {
180 sigil_length = 0;
181 }
182 else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
183 {
184 if (notescaped)
185 {
186 log_error(flags, "Failed at index %d [%s]: Unescaped sigil", i, argv[i]);
187 return COMMAND_LINE_ERROR;
188 }
189
190 sigil_length = 0;
191 escaped = FALSE;
192 notescaped = TRUE;
193 }
194 else
195 {
196 log_error(flags, "Failed at index %d [%s]: Invalid sigil", i, argv[i]);
197 return COMMAND_LINE_ERROR;
198 }
199
200 if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
201 (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
202 {
203 if (length < (sigil_length + 1))
204 {
205 if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
206 continue;
207
208 return COMMAND_LINE_ERROR_NO_KEYWORD;
209 }
210
211 keyword_index = sigil_length;
212 keyword = &argv[i][keyword_index];
213 toggle = -1;
214
215 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
216 {
217 if (strncmp(keyword, "enable-", 7) == 0)
218 {
219 toggle = TRUE;
220 keyword_index += 7;
221 keyword = &argv[i][keyword_index];
222 }
223 else if (strncmp(keyword, "disable-", 8) == 0)
224 {
225 toggle = FALSE;
226 keyword_index += 8;
227 keyword = &argv[i][keyword_index];
228 }
229 }
230
231 separator = NULL;
232
233 if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
234 separator = strchr(keyword, ':');
235
236 if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
237 separator = strchr(keyword, '=');
238
239 if (separator)
240 {
241 SSIZE_T separator_index = (separator - argv[i]);
242 SSIZE_T value_index = separator_index + 1;
243 keyword_length = WINPR_ASSERTING_INT_CAST(size_t, (separator - keyword));
244 value = &argv[i][value_index];
245 }
246 else
247 {
248 if (length < keyword_index)
249 {
250 log_error(flags, "Failed at index %d [%s]: Argument required", i, argv[i]);
251 return COMMAND_LINE_ERROR;
252 }
253
254 keyword_length = length - keyword_index;
255 value = NULL;
256 }
257
258 if (!escaped)
259 continue;
260
261 for (size_t j = 0; options[j].Name != NULL; j++)
262 {
263 COMMAND_LINE_ARGUMENT_A* cur = &options[j];
264 BOOL match = FALSE;
265
266 if (strncmp(cur->Name, keyword, keyword_length) == 0)
267 {
268 if (strlen(cur->Name) == keyword_length)
269 match = TRUE;
270 }
271
272 if ((!match) && (cur->Alias != NULL))
273 {
274 if (strncmp(cur->Alias, keyword, keyword_length) == 0)
275 {
276 if (strlen(cur->Alias) == keyword_length)
277 match = TRUE;
278 }
279 }
280
281 if (!match)
282 continue;
283
284 found = match;
285 cur->Index = i;
286
287 if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
288 {
289 BOOL argument = 0;
290 int value_present = 1;
291
292 if (flags & COMMAND_LINE_SIGIL_DASH)
293 {
294 if (strncmp(argv[i + 1], "-", 1) == 0)
295 value_present = 0;
296 }
297
298 if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
299 {
300 if (strncmp(argv[i + 1], "--", 2) == 0)
301 value_present = 0;
302 }
303
304 if (flags & COMMAND_LINE_SIGIL_SLASH)
305 {
306 if (strncmp(argv[i + 1], "/", 1) == 0)
307 value_present = 0;
308 }
309
310 if ((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
311 (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
312 argument = TRUE;
313 else
314 argument = FALSE;
315
316 if (value_present && argument)
317 {
318 i++;
319 value = argv[i];
320 }
321 else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
322 {
323 value = NULL;
324 }
325 else if (!value_present && argument)
326 {
327 log_error(flags, "Failed at index %d [%s]: Argument required", i, argv[i]);
328 return COMMAND_LINE_ERROR;
329 }
330 }
331
332 if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
333 {
334 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
335 {
336 log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
337 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
338 }
339 }
340 else
341 {
342 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
343 {
344 i--;
345 value = NULL;
346 }
347 }
348
349 if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
350 {
351 log_error(flags, "Failed at index %d [%s]: Missing value", i, argv[i]);
352 status = COMMAND_LINE_ERROR_MISSING_VALUE;
353 return status;
354 }
355
356 cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
357
358 if (value)
359 {
360 if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
361 {
362 log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
363 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
364 }
365
366 cur->Value = value;
367 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
368 }
369 else
370 {
371 if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
372 {
373 cur->Value = (LPSTR)1;
374 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
375 }
376 else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
377 {
378 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
379 {
380 if (toggle == -1)
381 cur->Value = BoolValueTrue;
382 else if (!toggle)
383 cur->Value = BoolValueFalse;
384 else
385 cur->Value = BoolValueTrue;
386 }
387 else
388 {
389 if (sigil[0] == '+')
390 cur->Value = BoolValueTrue;
391 else if (sigil[0] == '-')
392 cur->Value = BoolValueFalse;
393 else
394 cur->Value = BoolValueTrue;
395 }
396
397 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
398 }
399 }
400
401 if (postFilter)
402 {
403 count = postFilter(context, &options[j]);
404
405 if (count < 0)
406 {
407 log_error(flags,
408 "Failed at index %d [%s]: PostFilter rule could not be applied",
409 i, argv[i]);
410 status = COMMAND_LINE_ERROR;
411 return status;
412 }
413 }
414
415 if (cur->Flags & COMMAND_LINE_PRINT)
416 return COMMAND_LINE_STATUS_PRINT;
417 else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
418 return COMMAND_LINE_STATUS_PRINT_HELP;
419 else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
420 return COMMAND_LINE_STATUS_PRINT_VERSION;
421 else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
422 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
423 }
424
425 if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
426 {
427 log_error(flags, "Failed at index %d [%s]: Unexpected keyword", i, argv[i]);
428 return COMMAND_LINE_ERROR_NO_KEYWORD;
429 }
430 }
431 }
432
433 return status;
434}
435
436int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
437 WINPR_ATTR_UNUSED COMMAND_LINE_ARGUMENT_W* options,
438 WINPR_ATTR_UNUSED DWORD flags, WINPR_ATTR_UNUSED void* context,
439 WINPR_ATTR_UNUSED COMMAND_LINE_PRE_FILTER_FN_W preFilter,
440 WINPR_ATTR_UNUSED COMMAND_LINE_POST_FILTER_FN_W postFilter)
441{
442 WLog_ERR("TODO", "TODO: implement");
443 return 0;
444}
445
446int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
447{
448 for (size_t i = 0; options[i].Name != NULL; i++)
449 {
450 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
451 options[i].Value = NULL;
452 }
453
454 return 0;
455}
456
457int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
458{
459 for (int i = 0; options[i].Name != NULL; i++)
460 {
461 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
462 options[i].Value = NULL;
463 }
464
465 return 0;
466}
467
468const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
469 LPCSTR Name)
470{
471 WINPR_ASSERT(options);
472 WINPR_ASSERT(Name);
473
474 for (size_t i = 0; options[i].Name != NULL; i++)
475 {
476 if (strcmp(options[i].Name, Name) == 0)
477 return &options[i];
478
479 if (options[i].Alias != NULL)
480 {
481 if (strcmp(options[i].Alias, Name) == 0)
482 return &options[i];
483 }
484 }
485
486 return NULL;
487}
488
489const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
490 LPCWSTR Name)
491{
492 WINPR_ASSERT(options);
493 WINPR_ASSERT(Name);
494
495 for (size_t i = 0; options[i].Name != NULL; i++)
496 {
497 if (_wcscmp(options[i].Name, Name) == 0)
498 return &options[i];
499
500 if (options[i].Alias != NULL)
501 {
502 if (_wcscmp(options[i].Alias, Name) == 0)
503 return &options[i];
504 }
505 }
506
507 return NULL;
508}
509
510const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
511{
512 const COMMAND_LINE_ARGUMENT_A* nextArgument = NULL;
513
514 if (!argument || !argument->Name)
515 return NULL;
516
517 nextArgument = &argument[1];
518
519 if (nextArgument->Name == NULL)
520 return NULL;
521
522 return nextArgument;
523}
524
525static int is_quoted(char c)
526{
527 switch (c)
528 {
529 case '"':
530 return 1;
531 case '\'':
532 return -1;
533 default:
534 return 0;
535 }
536}
537
538static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
539{
540 size_t count = 0;
541 int quoted = 0;
542 bool escaped = false;
543 BOOL finished = FALSE;
544 BOOL first = TRUE;
545 const char* it = list;
546
547 if (!list)
548 return 0;
549 if (strlen(list) == 0)
550 return 0;
551
552 while (!finished)
553 {
554 BOOL nextFirst = FALSE;
555
556 const char cur = *it++;
557
558 /* Ignore the symbol that was escaped. */
559 if (escaped)
560 {
561 escaped = false;
562 continue;
563 }
564
565 switch (cur)
566 {
567 case '\0':
568 if (quoted != 0)
569 {
570 log_comma_error("Invalid argument (missing closing quote)", list);
571 *failed = TRUE;
572 return 0;
573 }
574 finished = TRUE;
575 break;
576 case '\\':
577 if (!escaped)
578 {
579 escaped = true;
580 continue;
581 }
582 break;
583 case '\'':
584 case '"':
585 if (!fullquoted)
586 {
587 int now = is_quoted(cur) && !escaped;
588 if (now == quoted)
589 quoted = 0;
590 else if (quoted == 0)
591 quoted = now;
592 }
593 break;
594 case ',':
595 if (first)
596 {
597 log_comma_error("Invalid argument (empty list elements)", list);
598 *failed = TRUE;
599 return 0;
600 }
601 if (quoted == 0)
602 {
603 nextFirst = TRUE;
604 count++;
605 }
606 break;
607 default:
608 break;
609 }
610
611 first = nextFirst;
612 }
613 return count + 1;
614}
615
616static char* get_next_comma(char* string, BOOL fullquoted)
617{
618 const char* log = string;
619 int quoted = 0;
620 bool first = true;
621 bool escaped = false;
622
623 WINPR_ASSERT(string);
624
625 while (TRUE)
626 {
627 char* last = string;
628 const char cur = *string++;
629 if (escaped)
630 {
631 escaped = false;
632 continue;
633 }
634
635 switch (cur)
636 {
637 case '\0':
638 if (quoted != 0)
639 log_comma_error("Invalid quoted argument", log);
640 return NULL;
641
642 case '\\':
643 if (!escaped)
644 {
645 escaped = true;
646 continue;
647 }
648 break;
649 case '\'':
650 case '"':
651 if (!fullquoted)
652 {
653 int now = is_quoted(cur);
654 if ((quoted == 0) && !first)
655 {
656 log_comma_error("Invalid quoted argument", log);
657 return NULL;
658 }
659 if (now == quoted)
660 quoted = 0;
661 else if (quoted == 0)
662 quoted = now;
663 }
664 break;
665
666 case ',':
667 if (first)
668 {
669 log_comma_error("Invalid argument (empty list elements)", log);
670 return NULL;
671 }
672 if (quoted == 0)
673 return last;
674 break;
675
676 default:
677 break;
678 }
679 first = FALSE;
680 }
681
682 return NULL;
683}
684
685static BOOL is_valid_fullquoted(const char* string)
686{
687 char cur = '\0';
688 char last = '\0';
689 const char quote = *string++;
690
691 /* We did not start with a quote. */
692 if (is_quoted(quote) == 0)
693 return FALSE;
694
695 while ((cur = *string++) != '\0')
696 {
697 /* A quote is found. */
698 if (cur == quote)
699 {
700 /* If the quote was escaped, it is valid. */
701 if (last != '\\')
702 {
703 /* Only allow unescaped quote as last character in string. */
704 if (*string != '\0')
705 return FALSE;
706 }
707 /* If the last quote in the string is escaped, it is wrong. */
708 else if (*string != '\0')
709 return FALSE;
710 }
711 last = cur;
712 }
713
714 /* The string did not terminate with the same quote as it started. */
715 if (last != quote)
716 return FALSE;
717 return TRUE;
718}
719
720char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
721{
722 char** p = NULL;
723 char* str = NULL;
724 size_t nArgs = 0;
725 size_t prefix = 0;
726 size_t len = 0;
727 size_t namelen = 0;
728 BOOL failed = FALSE;
729 char* copy = NULL;
730 char* unquoted = NULL;
731 BOOL fullquoted = FALSE;
732
733 BOOL success = FALSE;
734 if (count == NULL)
735 goto fail;
736
737 *count = 0;
738 if (list)
739 {
740 int start = 0;
741 int end = 0;
742 unquoted = copy = _strdup(list);
743 if (!copy)
744 goto fail;
745
746 len = strlen(unquoted);
747 if (len > 0)
748 {
749 start = is_quoted(unquoted[0]);
750 end = is_quoted(unquoted[len - 1]);
751
752 if ((start != 0) && (end != 0))
753 {
754 if (start != end)
755 {
756 log_comma_error("Invalid argument (quote mismatch)", list);
757 goto fail;
758 }
759 if (!is_valid_fullquoted(unquoted))
760 goto fail;
761 unquoted[len - 1] = '\0';
762 unquoted++;
763 len -= 2;
764 fullquoted = TRUE;
765 }
766 }
767 }
768
769 *count = get_element_count(unquoted, &failed, fullquoted);
770 if (failed)
771 goto fail;
772
773 if (*count == 0)
774 {
775 if (!name)
776 goto fail;
777 else
778 {
779 size_t clen = strlen(name);
780 p = (char**)calloc(2UL + clen, sizeof(char*));
781
782 if (p)
783 {
784 char* dst = (char*)&p[1];
785 p[0] = dst;
786 (void)sprintf_s(dst, clen + 1, "%s", name);
787 *count = 1;
788 success = TRUE;
789 goto fail;
790 }
791 }
792 }
793
794 nArgs = *count;
795
796 if (name)
797 nArgs++;
798
799 prefix = (nArgs + 1UL) * sizeof(char*);
800 if (name)
801 namelen = strlen(name);
802 p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
803
804 if (!p)
805 goto fail;
806
807 str = &((char*)p)[prefix];
808 memcpy(str, unquoted, len);
809
810 if (name)
811 {
812 char* namestr = &((char*)p)[prefix + len + 1];
813 memcpy(namestr, name, namelen);
814
815 p[0] = namestr;
816 }
817
818 for (size_t index = name ? 1 : 0; index < nArgs; index++)
819 {
820 char* ptr = str;
821 const int quote = is_quoted(*ptr);
822 char* comma = get_next_comma(str, fullquoted);
823
824 if ((quote != 0) && !fullquoted)
825 ptr++;
826
827 p[index] = ptr;
828
829 if (comma)
830 {
831 char* last = comma - 1;
832 const int lastQuote = is_quoted(*last);
833
834 if (!fullquoted)
835 {
836 if (lastQuote != quote)
837 {
838 log_comma_error("Invalid argument (quote mismatch)", list);
839 goto fail;
840 }
841 else if (lastQuote != 0)
842 *last = '\0';
843 }
844 *comma = '\0';
845
846 str = comma + 1;
847 }
848 else if (quote)
849 {
850 char* end = strrchr(ptr, '"');
851 if (!end)
852 goto fail;
853 *end = '\0';
854 }
855 }
856
857 *count = nArgs;
858 success = TRUE;
859fail:
860 free(copy);
861 if (!success)
862 {
863 if (count)
864 *count = 0;
865 free((void*)p);
866 return NULL;
867 }
868 return p;
869}
870
871char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
872{
873 return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
874}
875
876char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
877{
878 return CommandLineToCommaSeparatedValuesEx(argc, argv, NULL, 0);
879}
880
881static const char* filtered(const char* arg, const char* filters[], size_t number)
882{
883 if (number == 0)
884 return arg;
885 for (size_t x = 0; x < number; x++)
886 {
887 const char* filter = filters[x];
888 size_t len = strlen(filter);
889 if (_strnicmp(arg, filter, len) == 0)
890 return &arg[len];
891 }
892 return NULL;
893}
894
895char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
896 size_t number)
897{
898 char* str = NULL;
899 size_t offset = 0;
900 size_t size = WINPR_ASSERTING_INT_CAST(size_t, argc) + 1;
901 if ((argc <= 0) || !argv)
902 return NULL;
903
904 for (int x = 0; x < argc; x++)
905 size += strlen(argv[x]);
906
907 str = calloc(size, sizeof(char));
908 if (!str)
909 return NULL;
910 for (int x = 0; x < argc; x++)
911 {
912 int rc = 0;
913 const char* arg = filtered(argv[x], filters, number);
914 if (!arg)
915 continue;
916 rc = _snprintf(&str[offset], size - offset, "%s,", arg);
917 if (rc <= 0)
918 {
919 free(str);
920 return NULL;
921 }
922 offset += (size_t)rc;
923 }
924 if (offset > 0)
925 str[offset - 1] = '\0';
926 return str;
927}
928
929void CommandLineParserFree(char** ptr)
930{
931 union
932 {
933 char* p;
934 char** pp;
935 } uptr;
936 uptr.pp = ptr;
937 free(uptr.p);
938}