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 =
nullptr;
67 if (!WLog_IsLevelActive(log, level))
70 WLog_PrintTextMessage(log, level, line, file, fkt,
"Failed at index %d [%s]: %s", index,
71#
if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
81#define log_comma_error(msg, arg) log_comma_error_((msg), (arg), __FILE__, __func__, __LINE__)
82static void log_comma_error_(
const char* message, WINPR_ATTR_UNUSED
const char* argument,
83 const char* file,
const char* fkt,
size_t line)
85 const DWORD level = WLOG_ERROR;
86 static wLog* log =
nullptr;
90 if (!WLog_IsLevelActive(log, level))
93 WLog_PrintTextMessage(log, level, line, file, fkt,
"%s [%s]", message,
94#
if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
103 void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
104 COMMAND_LINE_POST_FILTER_FN_A postFilter)
108 BOOL notescaped = FALSE;
109 const char* sigil =
nullptr;
110 size_t sigil_length = 0;
111 char* keyword =
nullptr;
112 size_t keyword_index = 0;
113 char* separator =
nullptr;
114 char* value =
nullptr;
122 if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
125 status = COMMAND_LINE_STATUS_PRINT_HELP;
130 for (
int i = 1; i < argc; i++)
132 size_t keyword_length = 0;
138 count = preFilter(context, i, argc, argv);
142 log_error(flags,
"PreFilter rule could not be applied", i, argv[i]);
143 status = COMMAND_LINE_ERROR;
155 size_t length = strlen(argv[i]);
157 if ((sigil[0] ==
'/') && (flags & COMMAND_LINE_SIGIL_SLASH))
161 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_DASH))
167 if ((sigil[1] ==
'-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
171 else if ((sigil[0] ==
'+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
175 else if ((sigil[0] ==
'-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
179 else if (flags & COMMAND_LINE_SIGIL_NONE)
183 else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
187 log_error(flags,
"Unescaped sigil", i, argv[i]);
188 return COMMAND_LINE_ERROR;
197 log_error(flags,
"Invalid sigil", i, argv[i]);
198 return COMMAND_LINE_ERROR;
201 if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
202 (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
204 if (length < (sigil_length + 1))
206 if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
209 log_error(flags,
"Unexpected keyword", i, argv[i]);
210 return COMMAND_LINE_ERROR_NO_KEYWORD;
213 keyword_index = sigil_length;
214 keyword = &argv[i][keyword_index];
217 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
219 if (strncmp(keyword,
"enable-", 7) == 0)
223 keyword = &argv[i][keyword_index];
225 else if (strncmp(keyword,
"disable-", 8) == 0)
229 keyword = &argv[i][keyword_index];
235 if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
236 separator = strchr(keyword,
':');
238 if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
239 separator = strchr(keyword,
'=');
243 SSIZE_T separator_index = (separator - argv[i]);
244 SSIZE_T value_index = separator_index + 1;
245 keyword_length = WINPR_ASSERTING_INT_CAST(
size_t, (separator - keyword));
246 value = &argv[i][value_index];
250 if (length < keyword_index)
252 log_error(flags,
"Argument required", i, argv[i]);
253 return COMMAND_LINE_ERROR;
256 keyword_length = length - keyword_index;
263 for (
size_t j = 0; options[j].Name !=
nullptr; j++)
268 if (strncmp(cur->Name, keyword, keyword_length) == 0)
270 if (strlen(cur->Name) == keyword_length)
274 if ((!match) && (cur->Alias !=
nullptr))
276 if (strncmp(cur->Alias, keyword, keyword_length) == 0)
278 if (strlen(cur->Alias) == keyword_length)
289 if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
292 int value_present = 1;
294 if (flags & COMMAND_LINE_SIGIL_DASH)
296 if (strncmp(argv[i + 1],
"-", 1) == 0)
300 if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
302 if (strncmp(argv[i + 1],
"--", 2) == 0)
306 if (flags & COMMAND_LINE_SIGIL_SLASH)
308 if (strncmp(argv[i + 1],
"/", 1) == 0)
312 argument = (((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) != 0) ||
313 ((cur->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0));
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,
"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,
"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,
"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,
"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]);
406 log_error(flags,
"PostFilter rule could not be applied", i, argv[i]);
407 status = COMMAND_LINE_ERROR;
412 if (cur->Flags & COMMAND_LINE_PRINT)
413 return COMMAND_LINE_STATUS_PRINT;
414 else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
415 return COMMAND_LINE_STATUS_PRINT_HELP;
416 else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
417 return COMMAND_LINE_STATUS_PRINT_VERSION;
418 else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
419 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
422 if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
424 log_error(flags,
"Unexpected keyword", i, argv[i]);
425 return COMMAND_LINE_ERROR_NO_KEYWORD;
433int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED
int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
435 WINPR_ATTR_UNUSED DWORD flags, WINPR_ATTR_UNUSED
void* context,
436 WINPR_ATTR_UNUSED COMMAND_LINE_PRE_FILTER_FN_W preFilter,
437 WINPR_ATTR_UNUSED COMMAND_LINE_POST_FILTER_FN_W postFilter)
439 WLog_ERR(
"TODO",
"TODO: implement");
445 for (
size_t i = 0; options[i].Name !=
nullptr; i++)
447 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
448 options[i].Value =
nullptr;
456 for (
int i = 0; options[i].Name !=
nullptr; i++)
458 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
459 options[i].Value =
nullptr;
468 WINPR_ASSERT(options);
471 for (
size_t i = 0; options[i].Name !=
nullptr; i++)
473 if (strcmp(options[i].Name, Name) == 0)
476 if (options[i].Alias !=
nullptr)
478 if (strcmp(options[i].Alias, Name) == 0)
489 WINPR_ASSERT(options);
492 for (
size_t i = 0; options[i].Name !=
nullptr; i++)
494 if (_wcscmp(options[i].Name, Name) == 0)
497 if (options[i].Alias !=
nullptr)
499 if (_wcscmp(options[i].Alias, Name) == 0)
511 if (!argument || !argument->Name)
514 nextArgument = &argument[1];
516 if (nextArgument->Name ==
nullptr)
522static int is_quoted(
char c)
535static size_t get_element_count(
const char* list, BOOL* failed, BOOL fullquoted)
539 bool escaped =
false;
540 BOOL finished = FALSE;
542 const char* it = list;
546 if (strlen(list) == 0)
551 BOOL nextFirst = FALSE;
553 const char cur = *it++;
567 log_comma_error(
"Invalid argument (missing closing quote)", list);
584 int now = is_quoted(cur) && !escaped;
587 else if (quoted == 0)
594 log_comma_error(
"Invalid argument (empty list elements)", list);
613static char* get_next_comma(
char*
string, BOOL fullquoted)
615 const char* log = string;
618 bool escaped =
false;
620 WINPR_ASSERT(
string);
625 const char cur = *
string++;
636 log_comma_error(
"Invalid quoted argument", log);
650 int now = is_quoted(cur);
651 if ((quoted == 0) && !first)
653 log_comma_error(
"Invalid quoted argument", log);
658 else if (quoted == 0)
666 log_comma_error(
"Invalid argument (empty list elements)", log);
680static BOOL is_valid_fullquoted(
const char*
string)
684 const char quote = *
string++;
687 if (is_quoted(quote) == 0)
690 while ((cur = *
string++) !=
'\0')
703 else if (*
string !=
'\0')
710 return (last == quote);
713char** CommandLineParseCommaSeparatedValuesEx(
const char* name,
const char* list,
size_t* count)
722 char* copy =
nullptr;
723 char* unquoted =
nullptr;
724 BOOL fullquoted = FALSE;
726 BOOL success = FALSE;
727 if (count ==
nullptr)
735 unquoted = copy = _strdup(list);
739 len = strlen(unquoted);
742 start = is_quoted(unquoted[0]);
743 end = is_quoted(unquoted[len - 1]);
745 if ((start != 0) && (end != 0))
749 log_comma_error(
"Invalid argument (quote mismatch)", list);
752 if (!is_valid_fullquoted(unquoted))
754 unquoted[len - 1] =
'\0';
762 *count = get_element_count(unquoted, &failed, fullquoted);
772 size_t clen = strlen(name);
773 p = (
char**)calloc(2UL + clen,
sizeof(
char*));
777 char* dst = (
char*)&p[1];
779 (void)sprintf_s(dst, clen + 1,
"%s", name);
792 prefix = (nArgs + 1UL) *
sizeof(
char*);
794 namelen = strlen(name);
795 p = (
char**)calloc(len + prefix + 1 + namelen + 1,
sizeof(
char*));
800 str = &((
char*)p)[prefix];
801 memcpy(str, unquoted, len);
805 char* namestr = &((
char*)p)[prefix + len + 1];
806 memcpy(namestr, name, namelen);
811 for (
size_t index = name ? 1 : 0; index < nArgs; index++)
814 const int quote = is_quoted(*ptr);
815 char* comma = get_next_comma(str, fullquoted);
817 if ((quote != 0) && !fullquoted)
824 char* last = comma - 1;
825 const int lastQuote = is_quoted(*last);
829 if (lastQuote != quote)
831 log_comma_error(
"Invalid argument (quote mismatch)", list);
834 else if (lastQuote != 0)
843 char* end = strrchr(ptr,
'"');
864char** CommandLineParseCommaSeparatedValues(
const char* list,
size_t* count)
866 return CommandLineParseCommaSeparatedValuesEx(
nullptr, list, count);
869char* CommandLineToCommaSeparatedValues(
int argc,
char* argv[])
871 return CommandLineToCommaSeparatedValuesEx(argc, argv,
nullptr, 0);
874static const char* filtered(
const char* arg,
const char* filters[],
size_t number)
878 for (
size_t x = 0; x < number; x++)
880 const char* filter = filters[x];
881 size_t len = strlen(filter);
882 if (_strnicmp(arg, filter, len) == 0)
888char* CommandLineToCommaSeparatedValuesEx(
int argc,
char* argv[],
const char* filters[],
893 size_t size = WINPR_ASSERTING_INT_CAST(
size_t, argc) + 1;
894 if ((argc <= 0) || !argv)
897 for (
int x = 0; x < argc; x++)
898 size += strlen(argv[x]);
900 str = calloc(size,
sizeof(
char));
903 for (
int x = 0; x < argc; x++)
906 const char* arg = filtered(argv[x], filters, number);
909 rc = _snprintf(&str[offset], size - offset,
"%s,", arg);
915 offset += (size_t)rc;
918 str[offset - 1] =
'\0';
922void CommandLineParserFree(
char** ptr)