Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
931
third_party/FreeRDP/winpr/libwinpr/utils/cmdline.c
vendored
Normal file
931
third_party/FreeRDP/winpr/libwinpr/utils/cmdline.c
vendored
Normal file
@@ -0,0 +1,931 @@
|
||||
/**
|
||||
* WinPR: Windows Portable Runtime
|
||||
* Command-Line Utils
|
||||
*
|
||||
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <winpr/config.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include "../log.h"
|
||||
|
||||
#define TAG WINPR_TAG("commandline")
|
||||
|
||||
/**
|
||||
* Command-line syntax: some basic concepts:
|
||||
* https://pythonconquerstheuniverse.wordpress.com/2010/07/25/command-line-syntax-some-basic-concepts/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Command-Line Syntax:
|
||||
*
|
||||
* <sigil><keyword><separator><value>
|
||||
*
|
||||
* <sigil>: '/' or '-' or ('+' | '-')
|
||||
*
|
||||
* <keyword>: option, named argument, flag
|
||||
*
|
||||
* <separator>: ':' or '='
|
||||
*
|
||||
* <value>: argument value
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
|
||||
static const char censoredmessage[] =
|
||||
"<censored: build with -DWITH_DEBUG_UTILS_CMDLINE_DUMP=ON for details>";
|
||||
#endif
|
||||
|
||||
#define log_error(flags, msg, index, arg) \
|
||||
log_error_((flags), (msg), (index), (arg), __FILE__, __func__, __LINE__)
|
||||
static void log_error_(DWORD flags, LPCSTR message, int index, WINPR_ATTR_UNUSED LPCSTR argv,
|
||||
const char* file, const char* fkt, size_t line)
|
||||
{
|
||||
if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
|
||||
{
|
||||
const DWORD level = WLOG_ERROR;
|
||||
static wLog* log = nullptr;
|
||||
if (!log)
|
||||
log = WLog_Get(TAG);
|
||||
|
||||
if (!WLog_IsLevelActive(log, level))
|
||||
return;
|
||||
|
||||
WLog_PrintTextMessage(log, level, line, file, fkt, "Failed at index %d [%s]: %s", index,
|
||||
#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
|
||||
argv
|
||||
#else
|
||||
censoredmessage
|
||||
#endif
|
||||
,
|
||||
message);
|
||||
}
|
||||
}
|
||||
|
||||
#define log_comma_error(msg, arg) log_comma_error_((msg), (arg), __FILE__, __func__, __LINE__)
|
||||
static void log_comma_error_(const char* message, WINPR_ATTR_UNUSED const char* argument,
|
||||
const char* file, const char* fkt, size_t line)
|
||||
{
|
||||
const DWORD level = WLOG_ERROR;
|
||||
static wLog* log = nullptr;
|
||||
if (!log)
|
||||
log = WLog_Get(TAG);
|
||||
|
||||
if (!WLog_IsLevelActive(log, level))
|
||||
return;
|
||||
|
||||
WLog_PrintTextMessage(log, level, line, file, fkt, "%s [%s]", message,
|
||||
#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
|
||||
argument
|
||||
#else
|
||||
censoredmessage
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
|
||||
void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
|
||||
COMMAND_LINE_POST_FILTER_FN_A postFilter)
|
||||
{
|
||||
int status = 0;
|
||||
int count = 0;
|
||||
BOOL notescaped = FALSE;
|
||||
const char* sigil = nullptr;
|
||||
size_t sigil_length = 0;
|
||||
char* keyword = nullptr;
|
||||
size_t keyword_index = 0;
|
||||
char* separator = nullptr;
|
||||
char* value = nullptr;
|
||||
int toggle = 0;
|
||||
|
||||
if (!argv)
|
||||
return status;
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
|
||||
status = 0;
|
||||
else
|
||||
status = COMMAND_LINE_STATUS_PRINT_HELP;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
size_t keyword_length = 0;
|
||||
BOOL found = FALSE;
|
||||
BOOL escaped = TRUE;
|
||||
|
||||
if (preFilter)
|
||||
{
|
||||
count = preFilter(context, i, argc, argv);
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
log_error(flags, "PreFilter rule could not be applied", i, argv[i]);
|
||||
status = COMMAND_LINE_ERROR;
|
||||
return status;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
i += (count - 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sigil = argv[i];
|
||||
size_t length = strlen(argv[i]);
|
||||
|
||||
if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
|
||||
{
|
||||
sigil_length = 1;
|
||||
}
|
||||
else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
|
||||
{
|
||||
sigil_length = 1;
|
||||
|
||||
if (length > 2)
|
||||
{
|
||||
if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
|
||||
sigil_length = 2;
|
||||
}
|
||||
}
|
||||
else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
|
||||
{
|
||||
sigil_length = 1;
|
||||
}
|
||||
else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
|
||||
{
|
||||
sigil_length = 1;
|
||||
}
|
||||
else if (flags & COMMAND_LINE_SIGIL_NONE)
|
||||
{
|
||||
sigil_length = 0;
|
||||
}
|
||||
else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
|
||||
{
|
||||
if (notescaped)
|
||||
{
|
||||
log_error(flags, "Unescaped sigil", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR;
|
||||
}
|
||||
|
||||
sigil_length = 0;
|
||||
escaped = FALSE;
|
||||
notescaped = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error(flags, "Invalid sigil", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR;
|
||||
}
|
||||
|
||||
if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
|
||||
(flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
|
||||
{
|
||||
if (length < (sigil_length + 1))
|
||||
{
|
||||
if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
|
||||
continue;
|
||||
|
||||
log_error(flags, "Unexpected keyword", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR_NO_KEYWORD;
|
||||
}
|
||||
|
||||
keyword_index = sigil_length;
|
||||
keyword = &argv[i][keyword_index];
|
||||
toggle = -1;
|
||||
|
||||
if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
|
||||
{
|
||||
if (strncmp(keyword, "enable-", 7) == 0)
|
||||
{
|
||||
toggle = TRUE;
|
||||
keyword_index += 7;
|
||||
keyword = &argv[i][keyword_index];
|
||||
}
|
||||
else if (strncmp(keyword, "disable-", 8) == 0)
|
||||
{
|
||||
toggle = FALSE;
|
||||
keyword_index += 8;
|
||||
keyword = &argv[i][keyword_index];
|
||||
}
|
||||
}
|
||||
|
||||
separator = nullptr;
|
||||
|
||||
if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
|
||||
separator = strchr(keyword, ':');
|
||||
|
||||
if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
|
||||
separator = strchr(keyword, '=');
|
||||
|
||||
if (separator)
|
||||
{
|
||||
SSIZE_T separator_index = (separator - argv[i]);
|
||||
SSIZE_T value_index = separator_index + 1;
|
||||
keyword_length = WINPR_ASSERTING_INT_CAST(size_t, (separator - keyword));
|
||||
value = &argv[i][value_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (length < keyword_index)
|
||||
{
|
||||
log_error(flags, "Argument required", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR;
|
||||
}
|
||||
|
||||
keyword_length = length - keyword_index;
|
||||
value = nullptr;
|
||||
}
|
||||
|
||||
if (!escaped)
|
||||
continue;
|
||||
|
||||
for (size_t j = 0; options[j].Name != nullptr; j++)
|
||||
{
|
||||
COMMAND_LINE_ARGUMENT_A* cur = &options[j];
|
||||
BOOL match = FALSE;
|
||||
|
||||
if (strncmp(cur->Name, keyword, keyword_length) == 0)
|
||||
{
|
||||
if (strlen(cur->Name) == keyword_length)
|
||||
match = TRUE;
|
||||
}
|
||||
|
||||
if ((!match) && (cur->Alias != nullptr))
|
||||
{
|
||||
if (strncmp(cur->Alias, keyword, keyword_length) == 0)
|
||||
{
|
||||
if (strlen(cur->Alias) == keyword_length)
|
||||
match = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
continue;
|
||||
|
||||
found = match;
|
||||
cur->Index = i;
|
||||
|
||||
if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
|
||||
{
|
||||
BOOL argument = 0;
|
||||
int value_present = 1;
|
||||
|
||||
if (flags & COMMAND_LINE_SIGIL_DASH)
|
||||
{
|
||||
if (strncmp(argv[i + 1], "-", 1) == 0)
|
||||
value_present = 0;
|
||||
}
|
||||
|
||||
if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
|
||||
{
|
||||
if (strncmp(argv[i + 1], "--", 2) == 0)
|
||||
value_present = 0;
|
||||
}
|
||||
|
||||
if (flags & COMMAND_LINE_SIGIL_SLASH)
|
||||
{
|
||||
if (strncmp(argv[i + 1], "/", 1) == 0)
|
||||
value_present = 0;
|
||||
}
|
||||
|
||||
argument = (((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) != 0) ||
|
||||
((cur->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0));
|
||||
|
||||
if (value_present && argument)
|
||||
{
|
||||
i++;
|
||||
value = argv[i];
|
||||
}
|
||||
else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
|
||||
{
|
||||
value = nullptr;
|
||||
}
|
||||
else if (!value_present && argument)
|
||||
{
|
||||
log_error(flags, "Argument required", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
|
||||
{
|
||||
if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
|
||||
{
|
||||
log_error(flags, "Unexpected value", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
|
||||
{
|
||||
i--;
|
||||
value = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
|
||||
{
|
||||
log_error(flags, "Missing value", i, argv[i]);
|
||||
status = COMMAND_LINE_ERROR_MISSING_VALUE;
|
||||
return status;
|
||||
}
|
||||
|
||||
cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
|
||||
{
|
||||
log_error(flags, "Unexpected value", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
|
||||
}
|
||||
|
||||
cur->Value = value;
|
||||
cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
|
||||
{
|
||||
cur->Value = (LPSTR)1;
|
||||
cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
|
||||
}
|
||||
else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
|
||||
{
|
||||
if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
|
||||
{
|
||||
if (toggle == -1)
|
||||
cur->Value = BoolValueTrue;
|
||||
else if (!toggle)
|
||||
cur->Value = BoolValueFalse;
|
||||
else
|
||||
cur->Value = BoolValueTrue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sigil[0] == '+')
|
||||
cur->Value = BoolValueTrue;
|
||||
else if (sigil[0] == '-')
|
||||
cur->Value = BoolValueFalse;
|
||||
else
|
||||
cur->Value = BoolValueTrue;
|
||||
}
|
||||
|
||||
cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (postFilter)
|
||||
{
|
||||
count = postFilter(context, &options[j]);
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
log_error(flags, "PostFilter rule could not be applied", i, argv[i]);
|
||||
status = COMMAND_LINE_ERROR;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur->Flags & COMMAND_LINE_PRINT)
|
||||
return COMMAND_LINE_STATUS_PRINT;
|
||||
else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
|
||||
return COMMAND_LINE_STATUS_PRINT_HELP;
|
||||
else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
|
||||
return COMMAND_LINE_STATUS_PRINT_VERSION;
|
||||
else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
|
||||
return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
|
||||
}
|
||||
|
||||
if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
|
||||
{
|
||||
log_error(flags, "Unexpected keyword", i, argv[i]);
|
||||
return COMMAND_LINE_ERROR_NO_KEYWORD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
|
||||
WINPR_ATTR_UNUSED COMMAND_LINE_ARGUMENT_W* options,
|
||||
WINPR_ATTR_UNUSED DWORD flags, WINPR_ATTR_UNUSED void* context,
|
||||
WINPR_ATTR_UNUSED COMMAND_LINE_PRE_FILTER_FN_W preFilter,
|
||||
WINPR_ATTR_UNUSED COMMAND_LINE_POST_FILTER_FN_W postFilter)
|
||||
{
|
||||
WLog_ERR("TODO", "TODO: implement");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
|
||||
{
|
||||
for (size_t i = 0; options[i].Name != nullptr; i++)
|
||||
{
|
||||
options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
|
||||
options[i].Value = nullptr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
|
||||
{
|
||||
for (int i = 0; options[i].Name != nullptr; i++)
|
||||
{
|
||||
options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
|
||||
options[i].Value = nullptr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
|
||||
LPCSTR Name)
|
||||
{
|
||||
WINPR_ASSERT(options);
|
||||
WINPR_ASSERT(Name);
|
||||
|
||||
for (size_t i = 0; options[i].Name != nullptr; i++)
|
||||
{
|
||||
if (strcmp(options[i].Name, Name) == 0)
|
||||
return &options[i];
|
||||
|
||||
if (options[i].Alias != nullptr)
|
||||
{
|
||||
if (strcmp(options[i].Alias, Name) == 0)
|
||||
return &options[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
|
||||
LPCWSTR Name)
|
||||
{
|
||||
WINPR_ASSERT(options);
|
||||
WINPR_ASSERT(Name);
|
||||
|
||||
for (size_t i = 0; options[i].Name != nullptr; i++)
|
||||
{
|
||||
if (_wcscmp(options[i].Name, Name) == 0)
|
||||
return &options[i];
|
||||
|
||||
if (options[i].Alias != nullptr)
|
||||
{
|
||||
if (_wcscmp(options[i].Alias, Name) == 0)
|
||||
return &options[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
|
||||
{
|
||||
const COMMAND_LINE_ARGUMENT_A* nextArgument = nullptr;
|
||||
|
||||
if (!argument || !argument->Name)
|
||||
return nullptr;
|
||||
|
||||
nextArgument = &argument[1];
|
||||
|
||||
if (nextArgument->Name == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return nextArgument;
|
||||
}
|
||||
|
||||
static int is_quoted(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
return 1;
|
||||
case '\'':
|
||||
return -1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
|
||||
{
|
||||
size_t count = 0;
|
||||
int quoted = 0;
|
||||
bool escaped = false;
|
||||
BOOL finished = FALSE;
|
||||
BOOL first = TRUE;
|
||||
const char* it = list;
|
||||
|
||||
if (!list)
|
||||
return 0;
|
||||
if (strlen(list) == 0)
|
||||
return 0;
|
||||
|
||||
while (!finished)
|
||||
{
|
||||
BOOL nextFirst = FALSE;
|
||||
|
||||
const char cur = *it++;
|
||||
|
||||
/* Ignore the symbol that was escaped. */
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (cur)
|
||||
{
|
||||
case '\0':
|
||||
if (quoted != 0)
|
||||
{
|
||||
log_comma_error("Invalid argument (missing closing quote)", list);
|
||||
*failed = TRUE;
|
||||
return 0;
|
||||
}
|
||||
finished = TRUE;
|
||||
break;
|
||||
case '\\':
|
||||
if (!escaped)
|
||||
{
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
if (!fullquoted)
|
||||
{
|
||||
int now = is_quoted(cur) && !escaped;
|
||||
if (now == quoted)
|
||||
quoted = 0;
|
||||
else if (quoted == 0)
|
||||
quoted = now;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
if (first)
|
||||
{
|
||||
log_comma_error("Invalid argument (empty list elements)", list);
|
||||
*failed = TRUE;
|
||||
return 0;
|
||||
}
|
||||
if (quoted == 0)
|
||||
{
|
||||
nextFirst = TRUE;
|
||||
count++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
first = nextFirst;
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
static char* get_next_comma(char* string, BOOL fullquoted)
|
||||
{
|
||||
const char* log = string;
|
||||
int quoted = 0;
|
||||
bool first = true;
|
||||
bool escaped = false;
|
||||
|
||||
WINPR_ASSERT(string);
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
char* last = string;
|
||||
const char cur = *string++;
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (cur)
|
||||
{
|
||||
case '\0':
|
||||
if (quoted != 0)
|
||||
log_comma_error("Invalid quoted argument", log);
|
||||
return nullptr;
|
||||
|
||||
case '\\':
|
||||
if (!escaped)
|
||||
{
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
if (!fullquoted)
|
||||
{
|
||||
int now = is_quoted(cur);
|
||||
if ((quoted == 0) && !first)
|
||||
{
|
||||
log_comma_error("Invalid quoted argument", log);
|
||||
return nullptr;
|
||||
}
|
||||
if (now == quoted)
|
||||
quoted = 0;
|
||||
else if (quoted == 0)
|
||||
quoted = now;
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if (first)
|
||||
{
|
||||
log_comma_error("Invalid argument (empty list elements)", log);
|
||||
return nullptr;
|
||||
}
|
||||
if (quoted == 0)
|
||||
return last;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
first = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL is_valid_fullquoted(const char* string)
|
||||
{
|
||||
char cur = '\0';
|
||||
char last = '\0';
|
||||
const char quote = *string++;
|
||||
|
||||
/* We did not start with a quote. */
|
||||
if (is_quoted(quote) == 0)
|
||||
return FALSE;
|
||||
|
||||
while ((cur = *string++) != '\0')
|
||||
{
|
||||
/* A quote is found. */
|
||||
if (cur == quote)
|
||||
{
|
||||
/* If the quote was escaped, it is valid. */
|
||||
if (last != '\\')
|
||||
{
|
||||
/* Only allow unescaped quote as last character in string. */
|
||||
if (*string != '\0')
|
||||
return FALSE;
|
||||
}
|
||||
/* If the last quote in the string is escaped, it is wrong. */
|
||||
else if (*string != '\0')
|
||||
return FALSE;
|
||||
}
|
||||
last = cur;
|
||||
}
|
||||
|
||||
/* The string did not terminate with the same quote as it started. */
|
||||
return (last == quote);
|
||||
}
|
||||
|
||||
char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
|
||||
{
|
||||
char** p = nullptr;
|
||||
char* str = nullptr;
|
||||
size_t nArgs = 0;
|
||||
size_t prefix = 0;
|
||||
size_t len = 0;
|
||||
size_t namelen = 0;
|
||||
BOOL failed = FALSE;
|
||||
char* copy = nullptr;
|
||||
char* unquoted = nullptr;
|
||||
BOOL fullquoted = FALSE;
|
||||
|
||||
BOOL success = FALSE;
|
||||
if (count == nullptr)
|
||||
goto fail;
|
||||
|
||||
*count = 0;
|
||||
if (list)
|
||||
{
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
unquoted = copy = _strdup(list);
|
||||
if (!copy)
|
||||
goto fail;
|
||||
|
||||
len = strlen(unquoted);
|
||||
if (len > 0)
|
||||
{
|
||||
start = is_quoted(unquoted[0]);
|
||||
end = is_quoted(unquoted[len - 1]);
|
||||
|
||||
if ((start != 0) && (end != 0))
|
||||
{
|
||||
if (start != end)
|
||||
{
|
||||
log_comma_error("Invalid argument (quote mismatch)", list);
|
||||
goto fail;
|
||||
}
|
||||
if (!is_valid_fullquoted(unquoted))
|
||||
goto fail;
|
||||
unquoted[len - 1] = '\0';
|
||||
unquoted++;
|
||||
len -= 2;
|
||||
fullquoted = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*count = get_element_count(unquoted, &failed, fullquoted);
|
||||
if (failed)
|
||||
goto fail;
|
||||
|
||||
if (*count == 0)
|
||||
{
|
||||
if (!name)
|
||||
goto fail;
|
||||
else
|
||||
{
|
||||
size_t clen = strlen(name);
|
||||
p = (char**)calloc(2UL + clen, sizeof(char*));
|
||||
|
||||
if (p)
|
||||
{
|
||||
char* dst = (char*)&p[1];
|
||||
p[0] = dst;
|
||||
(void)sprintf_s(dst, clen + 1, "%s", name);
|
||||
*count = 1;
|
||||
success = TRUE;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nArgs = *count;
|
||||
|
||||
if (name)
|
||||
nArgs++;
|
||||
|
||||
prefix = (nArgs + 1UL) * sizeof(char*);
|
||||
if (name)
|
||||
namelen = strlen(name);
|
||||
p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
|
||||
|
||||
if (!p)
|
||||
goto fail;
|
||||
|
||||
str = &((char*)p)[prefix];
|
||||
memcpy(str, unquoted, len);
|
||||
|
||||
if (name)
|
||||
{
|
||||
char* namestr = &((char*)p)[prefix + len + 1];
|
||||
memcpy(namestr, name, namelen);
|
||||
|
||||
p[0] = namestr;
|
||||
}
|
||||
|
||||
for (size_t index = name ? 1 : 0; index < nArgs; index++)
|
||||
{
|
||||
char* ptr = str;
|
||||
const int quote = is_quoted(*ptr);
|
||||
char* comma = get_next_comma(str, fullquoted);
|
||||
|
||||
if ((quote != 0) && !fullquoted)
|
||||
ptr++;
|
||||
|
||||
p[index] = ptr;
|
||||
|
||||
if (comma)
|
||||
{
|
||||
char* last = comma - 1;
|
||||
const int lastQuote = is_quoted(*last);
|
||||
|
||||
if (!fullquoted)
|
||||
{
|
||||
if (lastQuote != quote)
|
||||
{
|
||||
log_comma_error("Invalid argument (quote mismatch)", list);
|
||||
goto fail;
|
||||
}
|
||||
else if (lastQuote != 0)
|
||||
*last = '\0';
|
||||
}
|
||||
*comma = '\0';
|
||||
|
||||
str = comma + 1;
|
||||
}
|
||||
else if (quote)
|
||||
{
|
||||
char* end = strrchr(ptr, '"');
|
||||
if (!end)
|
||||
goto fail;
|
||||
*end = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
*count = nArgs;
|
||||
success = TRUE;
|
||||
fail:
|
||||
free(copy);
|
||||
if (!success)
|
||||
{
|
||||
if (count)
|
||||
*count = 0;
|
||||
free((void*)p);
|
||||
return nullptr;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
|
||||
{
|
||||
return CommandLineParseCommaSeparatedValuesEx(nullptr, list, count);
|
||||
}
|
||||
|
||||
char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
|
||||
{
|
||||
return CommandLineToCommaSeparatedValuesEx(argc, argv, nullptr, 0);
|
||||
}
|
||||
|
||||
static const char* filtered(const char* arg, const char* filters[], size_t number)
|
||||
{
|
||||
if (number == 0)
|
||||
return arg;
|
||||
for (size_t x = 0; x < number; x++)
|
||||
{
|
||||
const char* filter = filters[x];
|
||||
size_t len = strlen(filter);
|
||||
if (_strnicmp(arg, filter, len) == 0)
|
||||
return &arg[len];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
|
||||
size_t number)
|
||||
{
|
||||
char* str = nullptr;
|
||||
size_t offset = 0;
|
||||
size_t size = WINPR_ASSERTING_INT_CAST(size_t, argc) + 1;
|
||||
if ((argc <= 0) || !argv)
|
||||
return nullptr;
|
||||
|
||||
for (int x = 0; x < argc; x++)
|
||||
size += strlen(argv[x]);
|
||||
|
||||
str = calloc(size, sizeof(char));
|
||||
if (!str)
|
||||
return nullptr;
|
||||
for (int x = 0; x < argc; x++)
|
||||
{
|
||||
int rc = 0;
|
||||
const char* arg = filtered(argv[x], filters, number);
|
||||
if (!arg)
|
||||
continue;
|
||||
rc = _snprintf(&str[offset], size - offset, "%s,", arg);
|
||||
if (rc <= 0)
|
||||
{
|
||||
free(str);
|
||||
return nullptr;
|
||||
}
|
||||
offset += (size_t)rc;
|
||||
}
|
||||
if (offset > 0)
|
||||
str[offset - 1] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
void CommandLineParserFree(char** ptr)
|
||||
{
|
||||
union
|
||||
{
|
||||
char* p;
|
||||
char** pp;
|
||||
} uptr;
|
||||
uptr.pp = ptr;
|
||||
free(uptr.p);
|
||||
}
|
||||
Reference in New Issue
Block a user