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_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)
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)
83 const DWORD level = WLOG_ERROR;
84 static wLog* log = NULL;
88 if (!WLog_IsLevelActive(log, level))
91 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
"%s [%s]", message,
92#
if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
101 void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
102 COMMAND_LINE_POST_FILTER_FN_A postFilter)
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;
120 if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
123 status = COMMAND_LINE_STATUS_PRINT_HELP;
128 for (
int i = 1; i < argc; i++)
130 size_t keyword_length = 0;
136 count = preFilter(context, i, argc, argv);
140 log_error(flags,
"Failed for index %d [%s]: PreFilter rule could not be applied", i,
142 status = COMMAND_LINE_ERROR;
154 size_t length = strlen(argv[i]);
156 if ((sigil[0] ==
'/') && (flags & COMMAND_LINE_SIGIL_SLASH))
160 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_DASH))
166 if ((sigil[1] ==
'-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
170 else if ((sigil[0] ==
'+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
174 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
178 else if (flags & COMMAND_LINE_SIGIL_NONE)
182 else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
186 log_error(flags,
"Failed at index %d [%s]: Unescaped sigil", i, argv[i]);
187 return COMMAND_LINE_ERROR;
196 log_error(flags,
"Failed at index %d [%s]: Invalid sigil", i, argv[i]);
197 return COMMAND_LINE_ERROR;
200 if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
201 (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
203 if (length < (sigil_length + 1))
205 if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
208 return COMMAND_LINE_ERROR_NO_KEYWORD;
211 keyword_index = sigil_length;
212 keyword = &argv[i][keyword_index];
215 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
217 if (strncmp(keyword,
"enable-", 7) == 0)
221 keyword = &argv[i][keyword_index];
223 else if (strncmp(keyword,
"disable-", 8) == 0)
227 keyword = &argv[i][keyword_index];
233 if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
234 separator = strchr(keyword,
':');
236 if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
237 separator = strchr(keyword,
'=');
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];
248 if (length < keyword_index)
250 log_error(flags,
"Failed at index %d [%s]: Argument required", i, argv[i]);
251 return COMMAND_LINE_ERROR;
254 keyword_length = length - keyword_index;
261 for (
size_t j = 0; options[j].Name != NULL; j++)
266 if (strncmp(cur->Name, keyword, keyword_length) == 0)
268 if (strlen(cur->Name) == keyword_length)
272 if ((!match) && (cur->Alias != NULL))
274 if (strncmp(cur->Alias, keyword, keyword_length) == 0)
276 if (strlen(cur->Alias) == keyword_length)
287 if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
290 int value_present = 1;
292 if (flags & COMMAND_LINE_SIGIL_DASH)
294 if (strncmp(argv[i + 1],
"-", 1) == 0)
298 if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
300 if (strncmp(argv[i + 1],
"--", 2) == 0)
304 if (flags & COMMAND_LINE_SIGIL_SLASH)
306 if (strncmp(argv[i + 1],
"/", 1) == 0)
310 if ((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
311 (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
316 if (value_present && argument)
321 else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
325 else if (!value_present && argument)
327 log_error(flags,
"Failed at index %d [%s]: Argument required", i, argv[i]);
328 return COMMAND_LINE_ERROR;
332 if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
334 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
336 log_error(flags,
"Failed at index %d [%s]: Unexpected value", i, argv[i]);
337 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
342 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
349 if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
351 log_error(flags,
"Failed at index %d [%s]: Missing value", i, argv[i]);
352 status = COMMAND_LINE_ERROR_MISSING_VALUE;
356 cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
360 if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
362 log_error(flags,
"Failed at index %d [%s]: Unexpected value", i, argv[i]);
363 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
367 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
371 if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
373 cur->Value = (LPSTR)1;
374 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
376 else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
378 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
381 cur->Value = BoolValueTrue;
383 cur->Value = BoolValueFalse;
385 cur->Value = BoolValueTrue;
390 cur->Value = BoolValueTrue;
391 else if (sigil[0] ==
'-')
392 cur->Value = BoolValueFalse;
394 cur->Value = BoolValueTrue;
397 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
403 count = postFilter(context, &options[j]);
408 "Failed at index %d [%s]: PostFilter rule could not be applied",
410 status = COMMAND_LINE_ERROR;
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;
425 if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
427 log_error(flags,
"Failed at index %d [%s]: Unexpected keyword", i, argv[i]);
428 return COMMAND_LINE_ERROR_NO_KEYWORD;
436int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED
int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
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)
442 WLog_ERR(
"TODO",
"TODO: implement");
448 for (
size_t i = 0; options[i].Name != NULL; i++)
450 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
451 options[i].Value = NULL;
459 for (
int i = 0; options[i].Name != NULL; i++)
461 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
462 options[i].Value = NULL;
471 WINPR_ASSERT(options);
474 for (
size_t i = 0; options[i].Name != NULL; i++)
476 if (strcmp(options[i].Name, Name) == 0)
479 if (options[i].Alias != NULL)
481 if (strcmp(options[i].Alias, Name) == 0)
492 WINPR_ASSERT(options);
495 for (
size_t i = 0; options[i].Name != NULL; i++)
497 if (_wcscmp(options[i].Name, Name) == 0)
500 if (options[i].Alias != NULL)
502 if (_wcscmp(options[i].Alias, Name) == 0)
514 if (!argument || !argument->Name)
517 nextArgument = &argument[1];
519 if (nextArgument->Name == NULL)
525static int is_quoted(
char c)
538static size_t get_element_count(
const char* list, BOOL* failed, BOOL fullquoted)
542 bool escaped =
false;
543 BOOL finished = FALSE;
545 const char* it = list;
549 if (strlen(list) == 0)
554 BOOL nextFirst = FALSE;
556 const char cur = *it++;
570 log_comma_error(
"Invalid argument (missing closing quote)", list);
587 int now = is_quoted(cur) && !escaped;
590 else if (quoted == 0)
597 log_comma_error(
"Invalid argument (empty list elements)", list);
616static char* get_next_comma(
char*
string, BOOL fullquoted)
618 const char* log = string;
621 bool escaped =
false;
623 WINPR_ASSERT(
string);
628 const char cur = *
string++;
639 log_comma_error(
"Invalid quoted argument", log);
653 int now = is_quoted(cur);
654 if ((quoted == 0) && !first)
656 log_comma_error(
"Invalid quoted argument", log);
661 else if (quoted == 0)
669 log_comma_error(
"Invalid argument (empty list elements)", log);
685static BOOL is_valid_fullquoted(
const char*
string)
689 const char quote = *
string++;
692 if (is_quoted(quote) == 0)
695 while ((cur = *
string++) !=
'\0')
708 else if (*
string !=
'\0')
720char** CommandLineParseCommaSeparatedValuesEx(
const char* name,
const char* list,
size_t* count)
730 char* unquoted = NULL;
731 BOOL fullquoted = FALSE;
733 BOOL success = FALSE;
742 unquoted = copy = _strdup(list);
746 len = strlen(unquoted);
749 start = is_quoted(unquoted[0]);
750 end = is_quoted(unquoted[len - 1]);
752 if ((start != 0) && (end != 0))
756 log_comma_error(
"Invalid argument (quote mismatch)", list);
759 if (!is_valid_fullquoted(unquoted))
761 unquoted[len - 1] =
'\0';
769 *count = get_element_count(unquoted, &failed, fullquoted);
779 size_t clen = strlen(name);
780 p = (
char**)calloc(2UL + clen,
sizeof(
char*));
784 char* dst = (
char*)&p[1];
786 (void)sprintf_s(dst, clen + 1,
"%s", name);
799 prefix = (nArgs + 1UL) *
sizeof(
char*);
801 namelen = strlen(name);
802 p = (
char**)calloc(len + prefix + 1 + namelen + 1,
sizeof(
char*));
807 str = &((
char*)p)[prefix];
808 memcpy(str, unquoted, len);
812 char* namestr = &((
char*)p)[prefix + len + 1];
813 memcpy(namestr, name, namelen);
818 for (
size_t index = name ? 1 : 0; index < nArgs; index++)
821 const int quote = is_quoted(*ptr);
822 char* comma = get_next_comma(str, fullquoted);
824 if ((quote != 0) && !fullquoted)
831 char* last = comma - 1;
832 const int lastQuote = is_quoted(*last);
836 if (lastQuote != quote)
838 log_comma_error(
"Invalid argument (quote mismatch)", list);
841 else if (lastQuote != 0)
850 char* end = strrchr(ptr,
'"');
871char** CommandLineParseCommaSeparatedValues(
const char* list,
size_t* count)
873 return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
876char* CommandLineToCommaSeparatedValues(
int argc,
char* argv[])
878 return CommandLineToCommaSeparatedValuesEx(argc, argv, NULL, 0);
881static const char* filtered(
const char* arg,
const char* filters[],
size_t number)
885 for (
size_t x = 0; x < number; x++)
887 const char* filter = filters[x];
888 size_t len = strlen(filter);
889 if (_strnicmp(arg, filter, len) == 0)
895char* CommandLineToCommaSeparatedValuesEx(
int argc,
char* argv[],
const char* filters[],
900 size_t size = WINPR_ASSERTING_INT_CAST(
size_t, argc) + 1;
901 if ((argc <= 0) || !argv)
904 for (
int x = 0; x < argc; x++)
905 size += strlen(argv[x]);
907 str = calloc(size,
sizeof(
char));
910 for (
int x = 0; x < argc; x++)
913 const char* arg = filtered(argv[x], filters, number);
916 rc = _snprintf(&str[offset], size - offset,
"%s,", arg);
922 offset += (size_t)rc;
925 str[offset - 1] =
'\0';
929void CommandLineParserFree(
char** ptr)