FreeRDP
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 static 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 
56 int 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 = (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 
392 int CommandLineParseArgumentsW(int argc, LPWSTR* argv, COMMAND_LINE_ARGUMENT_W* options,
393  DWORD flags, void* context, COMMAND_LINE_PRE_FILTER_FN_W preFilter,
394  COMMAND_LINE_POST_FILTER_FN_W postFilter)
395 {
396  return 0;
397 }
398 
399 int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
400 {
401  for (size_t i = 0; options[i].Name != NULL; i++)
402  {
403  options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
404  options[i].Value = NULL;
405  }
406 
407  return 0;
408 }
409 
410 int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
411 {
412  for (int i = 0; options[i].Name != NULL; i++)
413  {
414  options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
415  options[i].Value = NULL;
416  }
417 
418  return 0;
419 }
420 
421 const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
422  LPCSTR Name)
423 {
424  WINPR_ASSERT(options);
425  WINPR_ASSERT(Name);
426 
427  for (size_t i = 0; options[i].Name != NULL; i++)
428  {
429  if (strcmp(options[i].Name, Name) == 0)
430  return &options[i];
431 
432  if (options[i].Alias != NULL)
433  {
434  if (strcmp(options[i].Alias, Name) == 0)
435  return &options[i];
436  }
437  }
438 
439  return NULL;
440 }
441 
442 const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
443  LPCWSTR Name)
444 {
445  WINPR_ASSERT(options);
446  WINPR_ASSERT(Name);
447 
448  for (size_t i = 0; options[i].Name != NULL; i++)
449  {
450  if (_wcscmp(options[i].Name, Name) == 0)
451  return &options[i];
452 
453  if (options[i].Alias != NULL)
454  {
455  if (_wcscmp(options[i].Alias, Name) == 0)
456  return &options[i];
457  }
458  }
459 
460  return NULL;
461 }
462 
463 const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
464 {
465  const COMMAND_LINE_ARGUMENT_A* nextArgument = NULL;
466 
467  if (!argument || !argument->Name)
468  return NULL;
469 
470  nextArgument = &argument[1];
471 
472  if (nextArgument->Name == NULL)
473  return NULL;
474 
475  return nextArgument;
476 }
477 
478 static int is_quoted(char c)
479 {
480  switch (c)
481  {
482  case '"':
483  return 1;
484  case '\'':
485  return -1;
486  default:
487  return 0;
488  }
489 }
490 
491 static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
492 {
493  size_t count = 0;
494  int quoted = 0;
495  BOOL finished = FALSE;
496  BOOL first = TRUE;
497  const char* it = list;
498 
499  if (!list)
500  return 0;
501  if (strlen(list) == 0)
502  return 0;
503 
504  while (!finished)
505  {
506  BOOL nextFirst = FALSE;
507  switch (*it)
508  {
509  case '\0':
510  if (quoted != 0)
511  {
512  WLog_ERR(TAG, "Invalid argument (missing closing quote) '%s'", list);
513  *failed = TRUE;
514  return 0;
515  }
516  finished = TRUE;
517  break;
518  case '\'':
519  case '"':
520  if (!fullquoted)
521  {
522  int now = is_quoted(*it);
523  if (now == quoted)
524  quoted = 0;
525  else if (quoted == 0)
526  quoted = now;
527  }
528  break;
529  case ',':
530  if (first)
531  {
532  WLog_ERR(TAG, "Invalid argument (empty list elements) '%s'", list);
533  *failed = TRUE;
534  return 0;
535  }
536  if (quoted == 0)
537  {
538  nextFirst = TRUE;
539  count++;
540  }
541  break;
542  default:
543  break;
544  }
545 
546  first = nextFirst;
547  it++;
548  }
549  return count + 1;
550 }
551 
552 static char* get_next_comma(char* string, BOOL fullquoted)
553 {
554  const char* log = string;
555  int quoted = 0;
556  BOOL first = TRUE;
557 
558  WINPR_ASSERT(string);
559 
560  while (TRUE)
561  {
562  switch (*string)
563  {
564  case '\0':
565  if (quoted != 0)
566  WLog_ERR(TAG, "Invalid quoted argument '%s'", log);
567  return NULL;
568 
569  case '\'':
570  case '"':
571  if (!fullquoted)
572  {
573  int now = is_quoted(*string);
574  if ((quoted == 0) && !first)
575  {
576  WLog_ERR(TAG, "Invalid quoted argument '%s'", log);
577  return NULL;
578  }
579  if (now == quoted)
580  quoted = 0;
581  else if (quoted == 0)
582  quoted = now;
583  }
584  break;
585 
586  case ',':
587  if (first)
588  {
589  WLog_ERR(TAG, "Invalid argument (empty list elements) '%s'", log);
590  return NULL;
591  }
592  if (quoted == 0)
593  return string;
594  break;
595 
596  default:
597  break;
598  }
599  first = FALSE;
600  string++;
601  }
602 
603  return NULL;
604 }
605 
606 static BOOL is_valid_fullquoted(const char* string)
607 {
608  char cur = '\0';
609  char last = '\0';
610  const char quote = *string++;
611 
612  /* We did not start with a quote. */
613  if (is_quoted(quote) == 0)
614  return FALSE;
615 
616  while ((cur = *string++) != '\0')
617  {
618  /* A quote is found. */
619  if (cur == quote)
620  {
621  /* If the quote was escaped, it is valid. */
622  if (last != '\\')
623  {
624  /* Only allow unescaped quote as last character in string. */
625  if (*string != '\0')
626  return FALSE;
627  }
628  /* If the last quote in the string is escaped, it is wrong. */
629  else if (*string != '\0')
630  return FALSE;
631  }
632  last = cur;
633  }
634 
635  /* The string did not terminate with the same quote as it started. */
636  if (last != quote)
637  return FALSE;
638  return TRUE;
639 }
640 
641 char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
642 {
643  char** p = NULL;
644  char* str = NULL;
645  size_t nArgs = 0;
646  size_t prefix = 0;
647  size_t len = 0;
648  size_t namelen = 0;
649  BOOL failed = FALSE;
650  char* copy = NULL;
651  char* unquoted = NULL;
652  BOOL fullquoted = FALSE;
653 
654  BOOL success = FALSE;
655  if (count == NULL)
656  goto fail;
657 
658  *count = 0;
659  if (list)
660  {
661  int start = 0;
662  int end = 0;
663  unquoted = copy = _strdup(list);
664  if (!copy)
665  goto fail;
666 
667  len = strlen(unquoted);
668  if (len > 0)
669  {
670  start = is_quoted(unquoted[0]);
671  end = is_quoted(unquoted[len - 1]);
672 
673  if ((start != 0) && (end != 0))
674  {
675  if (start != end)
676  {
677  WLog_ERR(TAG, "invalid argument (quote mismatch) '%s'", list);
678  goto fail;
679  }
680  if (!is_valid_fullquoted(unquoted))
681  goto fail;
682  unquoted[len - 1] = '\0';
683  unquoted++;
684  len -= 2;
685  fullquoted = TRUE;
686  }
687  }
688  }
689 
690  *count = get_element_count(unquoted, &failed, fullquoted);
691  if (failed)
692  goto fail;
693 
694  if (*count == 0)
695  {
696  if (!name)
697  goto fail;
698  else
699  {
700  size_t clen = strlen(name);
701  p = (char**)calloc(2UL + clen, sizeof(char*));
702 
703  if (p)
704  {
705  char* dst = (char*)&p[1];
706  p[0] = dst;
707  (void)sprintf_s(dst, clen + 1, "%s", name);
708  *count = 1;
709  success = TRUE;
710  goto fail;
711  }
712  }
713  }
714 
715  nArgs = *count;
716 
717  if (name)
718  nArgs++;
719 
720  prefix = (nArgs + 1UL) * sizeof(char*);
721  if (name)
722  namelen = strlen(name);
723  p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
724 
725  if (!p)
726  goto fail;
727 
728  str = &((char*)p)[prefix];
729  memcpy(str, unquoted, len);
730 
731  if (name)
732  {
733  char* namestr = &((char*)p)[prefix + len + 1];
734  memcpy(namestr, name, namelen);
735 
736  p[0] = namestr;
737  }
738 
739  for (size_t index = name ? 1 : 0; index < nArgs; index++)
740  {
741  char* ptr = str;
742  const int quote = is_quoted(*ptr);
743  char* comma = get_next_comma(str, fullquoted);
744 
745  if ((quote != 0) && !fullquoted)
746  ptr++;
747 
748  p[index] = ptr;
749 
750  if (comma)
751  {
752  char* last = comma - 1;
753  const int lastQuote = is_quoted(*last);
754 
755  if (!fullquoted)
756  {
757  if (lastQuote != quote)
758  {
759  WLog_ERR(TAG, "invalid argument (quote mismatch) '%s'", list);
760  goto fail;
761  }
762  else if (lastQuote != 0)
763  *last = '\0';
764  }
765  *comma = '\0';
766 
767  str = comma + 1;
768  }
769  else if (quote)
770  {
771  char* end = strrchr(ptr, '"');
772  if (!end)
773  goto fail;
774  *end = '\0';
775  }
776  }
777 
778  *count = nArgs;
779  success = TRUE;
780 fail:
781  free(copy);
782  if (!success)
783  {
784  if (count)
785  *count = 0;
786  free(p);
787  return NULL;
788  }
789  return p;
790 }
791 
792 char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
793 {
794  return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
795 }
796 
797 char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
798 {
799  return CommandLineToCommaSeparatedValuesEx(argc, argv, NULL, 0);
800 }
801 
802 static const char* filtered(const char* arg, const char* filters[], size_t number)
803 {
804  if (number == 0)
805  return arg;
806  for (size_t x = 0; x < number; x++)
807  {
808  const char* filter = filters[x];
809  size_t len = strlen(filter);
810  if (_strnicmp(arg, filter, len) == 0)
811  return &arg[len];
812  }
813  return NULL;
814 }
815 
816 char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
817  size_t number)
818 {
819  char* str = NULL;
820  size_t offset = 0;
821  size_t size = argc + 1;
822  if ((argc <= 0) || !argv)
823  return NULL;
824 
825  for (int x = 0; x < argc; x++)
826  size += strlen(argv[x]);
827 
828  str = calloc(size, sizeof(char));
829  if (!str)
830  return NULL;
831  for (int x = 0; x < argc; x++)
832  {
833  int rc = 0;
834  const char* arg = filtered(argv[x], filters, number);
835  if (!arg)
836  continue;
837  rc = _snprintf(&str[offset], size - offset, "%s,", arg);
838  if (rc <= 0)
839  {
840  free(str);
841  return NULL;
842  }
843  offset += (size_t)rc;
844  }
845  if (offset > 0)
846  str[offset - 1] = '\0';
847  return str;
848 }
849 
850 void CommandLineParserFree(char** ptr)
851 {
852  union
853  {
854  char* p;
855  char** pp;
856  } uptr;
857  uptr.pp = ptr;
858  free(uptr.p);
859 }