20#include <winpr/config.h>
23#include <winpr/assert.h>
24#include <winpr/cmdline.h>
28#define TAG WINPR_TAG("commandline")
50#if !defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
51static const char censoredmessage[] =
52 "<censored: build with -DWITH_DEBUG_UTILS_CMDLINE_DUMP=ON for details>";
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)
60 if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
62 const DWORD level = WLOG_ERROR;
63 static wLog* log = NULL;
64 if (!WLog_IsLevelActive(log, level))
67 WLog_PrintTextMessage(log, level, line, file, fkt,
"Failed at index %d [%s]: %s", index,
68#
if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
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)
82 const DWORD level = WLOG_ERROR;
83 static wLog* log = NULL;
87 if (!WLog_IsLevelActive(log, level))
90 WLog_PrintTextMessage(log, level, line, file, fkt,
"%s [%s]", message,
91#
if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
100 void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
101 COMMAND_LINE_POST_FILTER_FN_A postFilter)
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;
119 if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
122 status = COMMAND_LINE_STATUS_PRINT_HELP;
127 for (
int i = 1; i < argc; i++)
129 size_t keyword_length = 0;
135 count = preFilter(context, i, argc, argv);
139 log_error(flags,
"Failed for index %d [%s]: PreFilter rule could not be applied", i,
141 status = COMMAND_LINE_ERROR;
153 size_t length = strlen(argv[i]);
155 if ((sigil[0] ==
'/') && (flags & COMMAND_LINE_SIGIL_SLASH))
159 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_DASH))
165 if ((sigil[1] ==
'-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
169 else if ((sigil[0] ==
'+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
173 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
177 else if (flags & COMMAND_LINE_SIGIL_NONE)
181 else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
185 log_error(flags,
"Failed at index %d [%s]: Unescaped sigil", i, argv[i]);
186 return COMMAND_LINE_ERROR;
195 log_error(flags,
"Failed at index %d [%s]: Invalid sigil", i, argv[i]);
196 return COMMAND_LINE_ERROR;
199 if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
200 (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
202 if (length < (sigil_length + 1))
204 if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
207 return COMMAND_LINE_ERROR_NO_KEYWORD;
210 keyword_index = sigil_length;
211 keyword = &argv[i][keyword_index];
214 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
216 if (strncmp(keyword,
"enable-", 7) == 0)
220 keyword = &argv[i][keyword_index];
222 else if (strncmp(keyword,
"disable-", 8) == 0)
226 keyword = &argv[i][keyword_index];
232 if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
233 separator = strchr(keyword,
':');
235 if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
236 separator = strchr(keyword,
'=');
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];
247 if (length < keyword_index)
249 log_error(flags,
"Failed at index %d [%s]: Argument required", i, argv[i]);
250 return COMMAND_LINE_ERROR;
253 keyword_length = length - keyword_index;
260 for (
size_t j = 0; options[j].Name != NULL; j++)
265 if (strncmp(cur->Name, keyword, keyword_length) == 0)
267 if (strlen(cur->Name) == keyword_length)
271 if ((!match) && (cur->Alias != NULL))
273 if (strncmp(cur->Alias, keyword, keyword_length) == 0)
275 if (strlen(cur->Alias) == keyword_length)
286 if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
289 int value_present = 1;
291 if (flags & COMMAND_LINE_SIGIL_DASH)
293 if (strncmp(argv[i + 1],
"-", 1) == 0)
297 if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
299 if (strncmp(argv[i + 1],
"--", 2) == 0)
303 if (flags & COMMAND_LINE_SIGIL_SLASH)
305 if (strncmp(argv[i + 1],
"/", 1) == 0)
309 if ((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
310 (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
315 if (value_present && argument)
320 else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
324 else if (!value_present && argument)
326 log_error(flags,
"Failed at index %d [%s]: Argument required", i, argv[i]);
327 return COMMAND_LINE_ERROR;
331 if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
333 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
335 log_error(flags,
"Failed at index %d [%s]: Unexpected value", i, argv[i]);
336 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
341 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
348 if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
350 log_error(flags,
"Failed at index %d [%s]: Missing value", i, argv[i]);
351 status = COMMAND_LINE_ERROR_MISSING_VALUE;
355 cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
359 if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
361 log_error(flags,
"Failed at index %d [%s]: Unexpected value", i, argv[i]);
362 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
366 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
370 if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
372 cur->Value = (LPSTR)1;
373 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
375 else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
377 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
380 cur->Value = BoolValueTrue;
382 cur->Value = BoolValueFalse;
384 cur->Value = BoolValueTrue;
389 cur->Value = BoolValueTrue;
390 else if (sigil[0] ==
'-')
391 cur->Value = BoolValueFalse;
393 cur->Value = BoolValueTrue;
396 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
402 count = postFilter(context, &options[j]);
407 "Failed at index %d [%s]: PostFilter rule could not be applied",
409 status = COMMAND_LINE_ERROR;
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;
424 if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
426 log_error(flags,
"Failed at index %d [%s]: Unexpected keyword", i, argv[i]);
427 return COMMAND_LINE_ERROR_NO_KEYWORD;
435int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED
int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
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)
441 WLog_ERR(
"TODO",
"TODO: implement");
447 for (
size_t i = 0; options[i].Name != NULL; i++)
449 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
450 options[i].Value = NULL;
458 for (
int i = 0; options[i].Name != NULL; i++)
460 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
461 options[i].Value = NULL;
470 WINPR_ASSERT(options);
473 for (
size_t i = 0; options[i].Name != NULL; i++)
475 if (strcmp(options[i].Name, Name) == 0)
478 if (options[i].Alias != NULL)
480 if (strcmp(options[i].Alias, Name) == 0)
491 WINPR_ASSERT(options);
494 for (
size_t i = 0; options[i].Name != NULL; i++)
496 if (_wcscmp(options[i].Name, Name) == 0)
499 if (options[i].Alias != NULL)
501 if (_wcscmp(options[i].Alias, Name) == 0)
513 if (!argument || !argument->Name)
516 nextArgument = &argument[1];
518 if (nextArgument->Name == NULL)
524static int is_quoted(
char c)
537static size_t get_element_count(
const char* list, BOOL* failed, BOOL fullquoted)
541 bool escaped =
false;
542 BOOL finished = FALSE;
544 const char* it = list;
548 if (strlen(list) == 0)
553 BOOL nextFirst = FALSE;
555 const char cur = *it++;
569 log_comma_error(
"Invalid argument (missing closing quote)", list);
586 int now = is_quoted(cur) && !escaped;
589 else if (quoted == 0)
596 log_comma_error(
"Invalid argument (empty list elements)", list);
615static char* get_next_comma(
char*
string, BOOL fullquoted)
617 const char* log = string;
620 bool escaped =
false;
622 WINPR_ASSERT(
string);
627 const char cur = *
string++;
638 log_comma_error(
"Invalid quoted argument", log);
652 int now = is_quoted(cur);
653 if ((quoted == 0) && !first)
655 log_comma_error(
"Invalid quoted argument", log);
660 else if (quoted == 0)
668 log_comma_error(
"Invalid argument (empty list elements)", log);
684static BOOL is_valid_fullquoted(
const char*
string)
688 const char quote = *
string++;
691 if (is_quoted(quote) == 0)
694 while ((cur = *
string++) !=
'\0')
707 else if (*
string !=
'\0')
719char** CommandLineParseCommaSeparatedValuesEx(
const char* name,
const char* list,
size_t* count)
729 char* unquoted = NULL;
730 BOOL fullquoted = FALSE;
732 BOOL success = FALSE;
741 unquoted = copy = _strdup(list);
745 len = strlen(unquoted);
748 start = is_quoted(unquoted[0]);
749 end = is_quoted(unquoted[len - 1]);
751 if ((start != 0) && (end != 0))
755 log_comma_error(
"Invalid argument (quote mismatch)", list);
758 if (!is_valid_fullquoted(unquoted))
760 unquoted[len - 1] =
'\0';
768 *count = get_element_count(unquoted, &failed, fullquoted);
778 size_t clen = strlen(name);
779 p = (
char**)calloc(2UL + clen,
sizeof(
char*));
783 char* dst = (
char*)&p[1];
785 (void)sprintf_s(dst, clen + 1,
"%s", name);
798 prefix = (nArgs + 1UL) *
sizeof(
char*);
800 namelen = strlen(name);
801 p = (
char**)calloc(len + prefix + 1 + namelen + 1,
sizeof(
char*));
806 str = &((
char*)p)[prefix];
807 memcpy(str, unquoted, len);
811 char* namestr = &((
char*)p)[prefix + len + 1];
812 memcpy(namestr, name, namelen);
817 for (
size_t index = name ? 1 : 0; index < nArgs; index++)
820 const int quote = is_quoted(*ptr);
821 char* comma = get_next_comma(str, fullquoted);
823 if ((quote != 0) && !fullquoted)
830 char* last = comma - 1;
831 const int lastQuote = is_quoted(*last);
835 if (lastQuote != quote)
837 log_comma_error(
"Invalid argument (quote mismatch)", list);
840 else if (lastQuote != 0)
849 char* end = strrchr(ptr,
'"');
870char** CommandLineParseCommaSeparatedValues(
const char* list,
size_t* count)
872 return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
875char* CommandLineToCommaSeparatedValues(
int argc,
char* argv[])
877 return CommandLineToCommaSeparatedValuesEx(argc, argv, NULL, 0);
880static const char* filtered(
const char* arg,
const char* filters[],
size_t number)
884 for (
size_t x = 0; x < number; x++)
886 const char* filter = filters[x];
887 size_t len = strlen(filter);
888 if (_strnicmp(arg, filter, len) == 0)
894char* CommandLineToCommaSeparatedValuesEx(
int argc,
char* argv[],
const char* filters[],
899 size_t size = WINPR_ASSERTING_INT_CAST(
size_t, argc) + 1;
900 if ((argc <= 0) || !argv)
903 for (
int x = 0; x < argc; x++)
904 size += strlen(argv[x]);
906 str = calloc(size,
sizeof(
char));
909 for (
int x = 0; x < argc; x++)
912 const char* arg = filtered(argv[x], filters, number);
915 rc = _snprintf(&str[offset], size - offset,
"%s,", arg);
921 offset += (size_t)rc;
924 str[offset - 1] =
'\0';
928void CommandLineParserFree(
char** ptr)