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