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