Coverage Report

Created: 2026-03-04 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/utils/cmdline.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Command-Line Utils
4
 *
5
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
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
0
#define TAG WINPR_TAG("commandline")
29
30
/**
31
 * Command-line syntax: some basic concepts:
32
 * https://pythonconquerstheuniverse.wordpress.com/2010/07/25/command-line-syntax-some-basic-concepts/
33
 */
34
35
/**
36
 * Command-Line Syntax:
37
 *
38
 * <sigil><keyword><separator><value>
39
 *
40
 * <sigil>: '/' or '-' or ('+' | '-')
41
 *
42
 * <keyword>: option, named argument, flag
43
 *
44
 * <separator>: ':' or '='
45
 *
46
 * <value>: argument value
47
 *
48
 */
49
50
#if !defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
51
static const char censoredmessage[] =
52
    "<censored: build with -DWITH_DEBUG_UTILS_CMDLINE_DUMP=ON for details>";
53
#endif
54
55
#define log_error(flags, msg, index, arg) \
56
0
  log_error_((flags), (msg), (index), (arg), __FILE__, __func__, __LINE__)
57
static void log_error_(DWORD flags, LPCSTR message, int index, WINPR_ATTR_UNUSED LPCSTR argv,
58
                       const char* file, const char* fkt, size_t line)
59
0
{
60
0
  if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
61
0
  {
62
0
    const DWORD level = WLOG_ERROR;
63
0
    static wLog* log = nullptr;
64
0
    if (!log)
65
0
      log = WLog_Get(TAG);
66
67
0
    if (!WLog_IsLevelActive(log, level))
68
0
      return;
69
70
0
    WLog_PrintTextMessage(log, level, line, file, fkt, "Failed at index %d [%s]: %s", index,
71
#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
72
                          argv
73
#else
74
0
                          censoredmessage
75
0
#endif
76
0
                          ,
77
0
                          message);
78
0
  }
79
0
}
80
81
0
#define log_comma_error(msg, arg) log_comma_error_((msg), (arg), __FILE__, __func__, __LINE__)
82
static void log_comma_error_(const char* message, WINPR_ATTR_UNUSED const char* argument,
83
                             const char* file, const char* fkt, size_t line)
84
0
{
85
0
  const DWORD level = WLOG_ERROR;
86
0
  static wLog* log = nullptr;
87
0
  if (!log)
88
0
    log = WLog_Get(TAG);
89
90
0
  if (!WLog_IsLevelActive(log, level))
91
0
    return;
92
93
0
  WLog_PrintTextMessage(log, level, line, file, fkt, "%s [%s]", message,
94
#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
95
                        argument
96
#else
97
0
                        censoredmessage
98
0
#endif
99
0
  );
100
0
}
101
102
int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
103
                               void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
104
                               COMMAND_LINE_POST_FILTER_FN_A postFilter)
105
0
{
106
0
  int status = 0;
107
0
  int count = 0;
108
0
  BOOL notescaped = FALSE;
109
0
  const char* sigil = nullptr;
110
0
  size_t sigil_length = 0;
111
0
  char* keyword = nullptr;
112
0
  size_t keyword_index = 0;
113
0
  char* separator = nullptr;
114
0
  char* value = nullptr;
115
0
  int toggle = 0;
116
117
0
  if (!argv)
118
0
    return status;
119
120
0
  if (argc == 1)
121
0
  {
122
0
    if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
123
0
      status = 0;
124
0
    else
125
0
      status = COMMAND_LINE_STATUS_PRINT_HELP;
126
127
0
    return status;
128
0
  }
129
130
0
  for (int i = 1; i < argc; i++)
131
0
  {
132
0
    size_t keyword_length = 0;
133
0
    BOOL found = FALSE;
134
0
    BOOL escaped = TRUE;
135
136
0
    if (preFilter)
137
0
    {
138
0
      count = preFilter(context, i, argc, argv);
139
140
0
      if (count < 0)
141
0
      {
142
0
        log_error(flags, "PreFilter rule could not be applied", i, argv[i]);
143
0
        status = COMMAND_LINE_ERROR;
144
0
        return status;
145
0
      }
146
147
0
      if (count > 0)
148
0
      {
149
0
        i += (count - 1);
150
0
        continue;
151
0
      }
152
0
    }
153
154
0
    sigil = argv[i];
155
0
    size_t length = strlen(argv[i]);
156
157
0
    if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
158
0
    {
159
0
      sigil_length = 1;
160
0
    }
161
0
    else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
162
0
    {
163
0
      sigil_length = 1;
164
165
0
      if (length > 2)
166
0
      {
167
0
        if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
168
0
          sigil_length = 2;
169
0
      }
170
0
    }
171
0
    else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
172
0
    {
173
0
      sigil_length = 1;
174
0
    }
175
0
    else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
176
0
    {
177
0
      sigil_length = 1;
178
0
    }
179
0
    else if (flags & COMMAND_LINE_SIGIL_NONE)
180
0
    {
181
0
      sigil_length = 0;
182
0
    }
183
0
    else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
184
0
    {
185
0
      if (notescaped)
186
0
      {
187
0
        log_error(flags, "Unescaped sigil", i, argv[i]);
188
0
        return COMMAND_LINE_ERROR;
189
0
      }
190
191
0
      sigil_length = 0;
192
0
      escaped = FALSE;
193
0
      notescaped = TRUE;
194
0
    }
195
0
    else
196
0
    {
197
0
      log_error(flags, "Invalid sigil", i, argv[i]);
198
0
      return COMMAND_LINE_ERROR;
199
0
    }
200
201
0
    if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
202
0
        (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
203
0
    {
204
0
      if (length < (sigil_length + 1))
205
0
      {
206
0
        if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
207
0
          continue;
208
209
0
        log_error(flags, "Unexpected keyword", i, argv[i]);
210
0
        return COMMAND_LINE_ERROR_NO_KEYWORD;
211
0
      }
212
213
0
      keyword_index = sigil_length;
214
0
      keyword = &argv[i][keyword_index];
215
0
      toggle = -1;
216
217
0
      if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
218
0
      {
219
0
        if (strncmp(keyword, "enable-", 7) == 0)
220
0
        {
221
0
          toggle = TRUE;
222
0
          keyword_index += 7;
223
0
          keyword = &argv[i][keyword_index];
224
0
        }
225
0
        else if (strncmp(keyword, "disable-", 8) == 0)
226
0
        {
227
0
          toggle = FALSE;
228
0
          keyword_index += 8;
229
0
          keyword = &argv[i][keyword_index];
230
0
        }
231
0
      }
232
233
0
      separator = nullptr;
234
235
0
      if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
236
0
        separator = strchr(keyword, ':');
237
238
0
      if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
239
0
        separator = strchr(keyword, '=');
240
241
0
      if (separator)
242
0
      {
243
0
        SSIZE_T separator_index = (separator - argv[i]);
244
0
        SSIZE_T value_index = separator_index + 1;
245
0
        keyword_length = WINPR_ASSERTING_INT_CAST(size_t, (separator - keyword));
246
0
        value = &argv[i][value_index];
247
0
      }
248
0
      else
249
0
      {
250
0
        if (length < keyword_index)
251
0
        {
252
0
          log_error(flags, "Argument required", i, argv[i]);
253
0
          return COMMAND_LINE_ERROR;
254
0
        }
255
256
0
        keyword_length = length - keyword_index;
257
0
        value = nullptr;
258
0
      }
259
260
0
      if (!escaped)
261
0
        continue;
262
263
0
      for (size_t j = 0; options[j].Name != nullptr; j++)
264
0
      {
265
0
        COMMAND_LINE_ARGUMENT_A* cur = &options[j];
266
0
        BOOL match = FALSE;
267
268
0
        if (strncmp(cur->Name, keyword, keyword_length) == 0)
269
0
        {
270
0
          if (strlen(cur->Name) == keyword_length)
271
0
            match = TRUE;
272
0
        }
273
274
0
        if ((!match) && (cur->Alias != nullptr))
275
0
        {
276
0
          if (strncmp(cur->Alias, keyword, keyword_length) == 0)
277
0
          {
278
0
            if (strlen(cur->Alias) == keyword_length)
279
0
              match = TRUE;
280
0
          }
281
0
        }
282
283
0
        if (!match)
284
0
          continue;
285
286
0
        found = match;
287
0
        cur->Index = i;
288
289
0
        if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
290
0
        {
291
0
          BOOL argument = 0;
292
0
          int value_present = 1;
293
294
0
          if (flags & COMMAND_LINE_SIGIL_DASH)
295
0
          {
296
0
            if (strncmp(argv[i + 1], "-", 1) == 0)
297
0
              value_present = 0;
298
0
          }
299
300
0
          if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
301
0
          {
302
0
            if (strncmp(argv[i + 1], "--", 2) == 0)
303
0
              value_present = 0;
304
0
          }
305
306
0
          if (flags & COMMAND_LINE_SIGIL_SLASH)
307
0
          {
308
0
            if (strncmp(argv[i + 1], "/", 1) == 0)
309
0
              value_present = 0;
310
0
          }
311
312
0
          argument = (((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) != 0) ||
313
0
                      ((cur->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0));
314
315
0
          if (value_present && argument)
316
0
          {
317
0
            i++;
318
0
            value = argv[i];
319
0
          }
320
0
          else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
321
0
          {
322
0
            value = nullptr;
323
0
          }
324
0
          else if (!value_present && argument)
325
0
          {
326
0
            log_error(flags, "Argument required", i, argv[i]);
327
0
            return COMMAND_LINE_ERROR;
328
0
          }
329
0
        }
330
331
0
        if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
332
0
        {
333
0
          if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
334
0
          {
335
0
            log_error(flags, "Unexpected value", i, argv[i]);
336
0
            return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
337
0
          }
338
0
        }
339
0
        else
340
0
        {
341
0
          if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
342
0
          {
343
0
            i--;
344
0
            value = nullptr;
345
0
          }
346
0
        }
347
348
0
        if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
349
0
        {
350
0
          log_error(flags, "Missing value", i, argv[i]);
351
0
          status = COMMAND_LINE_ERROR_MISSING_VALUE;
352
0
          return status;
353
0
        }
354
355
0
        cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
356
357
0
        if (value)
358
0
        {
359
0
          if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
360
0
          {
361
0
            log_error(flags, "Unexpected value", i, argv[i]);
362
0
            return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
363
0
          }
364
365
0
          cur->Value = value;
366
0
          cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
367
0
        }
368
0
        else
369
0
        {
370
0
          if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
371
0
          {
372
0
            cur->Value = (LPSTR)1;
373
0
            cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
374
0
          }
375
0
          else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
376
0
          {
377
0
            if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
378
0
            {
379
0
              if (toggle == -1)
380
0
                cur->Value = BoolValueTrue;
381
0
              else if (!toggle)
382
0
                cur->Value = BoolValueFalse;
383
0
              else
384
0
                cur->Value = BoolValueTrue;
385
0
            }
386
0
            else
387
0
            {
388
0
              if (sigil[0] == '+')
389
0
                cur->Value = BoolValueTrue;
390
0
              else if (sigil[0] == '-')
391
0
                cur->Value = BoolValueFalse;
392
0
              else
393
0
                cur->Value = BoolValueTrue;
394
0
            }
395
396
0
            cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
397
0
          }
398
0
        }
399
400
0
        if (postFilter)
401
0
        {
402
0
          count = postFilter(context, &options[j]);
403
404
0
          if (count < 0)
405
0
          {
406
0
            log_error(flags, "PostFilter rule could not be applied", i, argv[i]);
407
0
            status = COMMAND_LINE_ERROR;
408
0
            return status;
409
0
          }
410
0
        }
411
412
0
        if (cur->Flags & COMMAND_LINE_PRINT)
413
0
          return COMMAND_LINE_STATUS_PRINT;
414
0
        else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
415
0
          return COMMAND_LINE_STATUS_PRINT_HELP;
416
0
        else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
417
0
          return COMMAND_LINE_STATUS_PRINT_VERSION;
418
0
        else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
419
0
          return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
420
0
      }
421
422
0
      if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
423
0
      {
424
0
        log_error(flags, "Unexpected keyword", i, argv[i]);
425
0
        return COMMAND_LINE_ERROR_NO_KEYWORD;
426
0
      }
427
0
    }
428
0
  }
429
430
0
  return status;
431
0
}
432
433
int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
434
                               WINPR_ATTR_UNUSED COMMAND_LINE_ARGUMENT_W* options,
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)
438
0
{
439
0
  WLog_ERR("TODO", "TODO: implement");
440
0
  return 0;
441
0
}
442
443
int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
444
0
{
445
0
  for (size_t i = 0; options[i].Name != nullptr; i++)
446
0
  {
447
0
    options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
448
0
    options[i].Value = nullptr;
449
0
  }
450
451
0
  return 0;
452
0
}
453
454
int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
455
0
{
456
0
  for (int i = 0; options[i].Name != nullptr; i++)
457
0
  {
458
0
    options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
459
0
    options[i].Value = nullptr;
460
0
  }
461
462
0
  return 0;
463
0
}
464
465
const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
466
                                                        LPCSTR Name)
467
0
{
468
0
  WINPR_ASSERT(options);
469
0
  WINPR_ASSERT(Name);
470
471
0
  for (size_t i = 0; options[i].Name != nullptr; i++)
472
0
  {
473
0
    if (strcmp(options[i].Name, Name) == 0)
474
0
      return &options[i];
475
476
0
    if (options[i].Alias != nullptr)
477
0
    {
478
0
      if (strcmp(options[i].Alias, Name) == 0)
479
0
        return &options[i];
480
0
    }
481
0
  }
482
483
0
  return nullptr;
484
0
}
485
486
const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
487
                                                        LPCWSTR Name)
488
0
{
489
0
  WINPR_ASSERT(options);
490
0
  WINPR_ASSERT(Name);
491
492
0
  for (size_t i = 0; options[i].Name != nullptr; i++)
493
0
  {
494
0
    if (_wcscmp(options[i].Name, Name) == 0)
495
0
      return &options[i];
496
497
0
    if (options[i].Alias != nullptr)
498
0
    {
499
0
      if (_wcscmp(options[i].Alias, Name) == 0)
500
0
        return &options[i];
501
0
    }
502
0
  }
503
504
0
  return nullptr;
505
0
}
506
507
const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
508
0
{
509
0
  const COMMAND_LINE_ARGUMENT_A* nextArgument = nullptr;
510
511
0
  if (!argument || !argument->Name)
512
0
    return nullptr;
513
514
0
  nextArgument = &argument[1];
515
516
0
  if (nextArgument->Name == nullptr)
517
0
    return nullptr;
518
519
0
  return nextArgument;
520
0
}
521
522
static int is_quoted(char c)
523
0
{
524
0
  switch (c)
525
0
  {
526
0
    case '"':
527
0
      return 1;
528
0
    case '\'':
529
0
      return -1;
530
0
    default:
531
0
      return 0;
532
0
  }
533
0
}
534
535
static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
536
0
{
537
0
  size_t count = 0;
538
0
  int quoted = 0;
539
0
  bool escaped = false;
540
0
  BOOL finished = FALSE;
541
0
  BOOL first = TRUE;
542
0
  const char* it = list;
543
544
0
  if (!list)
545
0
    return 0;
546
0
  if (strlen(list) == 0)
547
0
    return 0;
548
549
0
  while (!finished)
550
0
  {
551
0
    BOOL nextFirst = FALSE;
552
553
0
    const char cur = *it++;
554
555
    /* Ignore the symbol that was escaped. */
556
0
    if (escaped)
557
0
    {
558
0
      escaped = false;
559
0
      continue;
560
0
    }
561
562
0
    switch (cur)
563
0
    {
564
0
      case '\0':
565
0
        if (quoted != 0)
566
0
        {
567
0
          log_comma_error("Invalid argument (missing closing quote)", list);
568
0
          *failed = TRUE;
569
0
          return 0;
570
0
        }
571
0
        finished = TRUE;
572
0
        break;
573
0
      case '\\':
574
0
        if (!escaped)
575
0
        {
576
0
          escaped = true;
577
0
          continue;
578
0
        }
579
0
        break;
580
0
      case '\'':
581
0
      case '"':
582
0
        if (!fullquoted)
583
0
        {
584
0
          int now = is_quoted(cur) && !escaped;
585
0
          if (now == quoted)
586
0
            quoted = 0;
587
0
          else if (quoted == 0)
588
0
            quoted = now;
589
0
        }
590
0
        break;
591
0
      case ',':
592
0
        if (first)
593
0
        {
594
0
          log_comma_error("Invalid argument (empty list elements)", list);
595
0
          *failed = TRUE;
596
0
          return 0;
597
0
        }
598
0
        if (quoted == 0)
599
0
        {
600
0
          nextFirst = TRUE;
601
0
          count++;
602
0
        }
603
0
        break;
604
0
      default:
605
0
        break;
606
0
    }
607
608
0
    first = nextFirst;
609
0
  }
610
0
  return count + 1;
611
0
}
612
613
static char* get_next_comma(char* string, BOOL fullquoted)
614
0
{
615
0
  const char* log = string;
616
0
  int quoted = 0;
617
0
  bool first = true;
618
0
  bool escaped = false;
619
620
0
  WINPR_ASSERT(string);
621
622
0
  while (TRUE)
623
0
  {
624
0
    char* last = string;
625
0
    const char cur = *string++;
626
0
    if (escaped)
627
0
    {
628
0
      escaped = false;
629
0
      continue;
630
0
    }
631
632
0
    switch (cur)
633
0
    {
634
0
      case '\0':
635
0
        if (quoted != 0)
636
0
          log_comma_error("Invalid quoted argument", log);
637
0
        return nullptr;
638
639
0
      case '\\':
640
0
        if (!escaped)
641
0
        {
642
0
          escaped = true;
643
0
          continue;
644
0
        }
645
0
        break;
646
0
      case '\'':
647
0
      case '"':
648
0
        if (!fullquoted)
649
0
        {
650
0
          int now = is_quoted(cur);
651
0
          if ((quoted == 0) && !first)
652
0
          {
653
0
            log_comma_error("Invalid quoted argument", log);
654
0
            return nullptr;
655
0
          }
656
0
          if (now == quoted)
657
0
            quoted = 0;
658
0
          else if (quoted == 0)
659
0
            quoted = now;
660
0
        }
661
0
        break;
662
663
0
      case ',':
664
0
        if (first)
665
0
        {
666
0
          log_comma_error("Invalid argument (empty list elements)", log);
667
0
          return nullptr;
668
0
        }
669
0
        if (quoted == 0)
670
0
          return last;
671
0
        break;
672
673
0
      default:
674
0
        break;
675
0
    }
676
0
    first = FALSE;
677
0
  }
678
0
}
679
680
static BOOL is_valid_fullquoted(const char* string)
681
0
{
682
0
  char cur = '\0';
683
0
  char last = '\0';
684
0
  const char quote = *string++;
685
686
  /* We did not start with a quote. */
687
0
  if (is_quoted(quote) == 0)
688
0
    return FALSE;
689
690
0
  while ((cur = *string++) != '\0')
691
0
  {
692
    /* A quote is found. */
693
0
    if (cur == quote)
694
0
    {
695
      /* If the quote was escaped, it is valid. */
696
0
      if (last != '\\')
697
0
      {
698
        /* Only allow unescaped quote as last character in string. */
699
0
        if (*string != '\0')
700
0
          return FALSE;
701
0
      }
702
      /* If the last quote in the string is escaped, it is wrong. */
703
0
      else if (*string != '\0')
704
0
        return FALSE;
705
0
    }
706
0
    last = cur;
707
0
  }
708
709
  /* The string did not terminate with the same quote as it started. */
710
0
  return (last == quote);
711
0
}
712
713
char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
714
0
{
715
0
  char** p = nullptr;
716
0
  char* str = nullptr;
717
0
  size_t nArgs = 0;
718
0
  size_t prefix = 0;
719
0
  size_t len = 0;
720
0
  size_t namelen = 0;
721
0
  BOOL failed = FALSE;
722
0
  char* copy = nullptr;
723
0
  char* unquoted = nullptr;
724
0
  BOOL fullquoted = FALSE;
725
726
0
  BOOL success = FALSE;
727
0
  if (count == nullptr)
728
0
    goto fail;
729
730
0
  *count = 0;
731
0
  if (list)
732
0
  {
733
0
    int start = 0;
734
0
    int end = 0;
735
0
    unquoted = copy = _strdup(list);
736
0
    if (!copy)
737
0
      goto fail;
738
739
0
    len = strlen(unquoted);
740
0
    if (len > 0)
741
0
    {
742
0
      start = is_quoted(unquoted[0]);
743
0
      end = is_quoted(unquoted[len - 1]);
744
745
0
      if ((start != 0) && (end != 0))
746
0
      {
747
0
        if (start != end)
748
0
        {
749
0
          log_comma_error("Invalid argument (quote mismatch)", list);
750
0
          goto fail;
751
0
        }
752
0
        if (!is_valid_fullquoted(unquoted))
753
0
          goto fail;
754
0
        unquoted[len - 1] = '\0';
755
0
        unquoted++;
756
0
        len -= 2;
757
0
        fullquoted = TRUE;
758
0
      }
759
0
    }
760
0
  }
761
762
0
  *count = get_element_count(unquoted, &failed, fullquoted);
763
0
  if (failed)
764
0
    goto fail;
765
766
0
  if (*count == 0)
767
0
  {
768
0
    if (!name)
769
0
      goto fail;
770
0
    else
771
0
    {
772
0
      size_t clen = strlen(name);
773
0
      p = (char**)calloc(2UL + clen, sizeof(char*));
774
775
0
      if (p)
776
0
      {
777
0
        char* dst = (char*)&p[1];
778
0
        p[0] = dst;
779
0
        (void)sprintf_s(dst, clen + 1, "%s", name);
780
0
        *count = 1;
781
0
        success = TRUE;
782
0
        goto fail;
783
0
      }
784
0
    }
785
0
  }
786
787
0
  nArgs = *count;
788
789
0
  if (name)
790
0
    nArgs++;
791
792
0
  prefix = (nArgs + 1UL) * sizeof(char*);
793
0
  if (name)
794
0
    namelen = strlen(name);
795
0
  p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
796
797
0
  if (!p)
798
0
    goto fail;
799
800
0
  str = &((char*)p)[prefix];
801
0
  memcpy(str, unquoted, len);
802
803
0
  if (name)
804
0
  {
805
0
    char* namestr = &((char*)p)[prefix + len + 1];
806
0
    memcpy(namestr, name, namelen);
807
808
0
    p[0] = namestr;
809
0
  }
810
811
0
  for (size_t index = name ? 1 : 0; index < nArgs; index++)
812
0
  {
813
0
    char* ptr = str;
814
0
    const int quote = is_quoted(*ptr);
815
0
    char* comma = get_next_comma(str, fullquoted);
816
817
0
    if ((quote != 0) && !fullquoted)
818
0
      ptr++;
819
820
0
    p[index] = ptr;
821
822
0
    if (comma)
823
0
    {
824
0
      char* last = comma - 1;
825
0
      const int lastQuote = is_quoted(*last);
826
827
0
      if (!fullquoted)
828
0
      {
829
0
        if (lastQuote != quote)
830
0
        {
831
0
          log_comma_error("Invalid argument (quote mismatch)", list);
832
0
          goto fail;
833
0
        }
834
0
        else if (lastQuote != 0)
835
0
          *last = '\0';
836
0
      }
837
0
      *comma = '\0';
838
839
0
      str = comma + 1;
840
0
    }
841
0
    else if (quote)
842
0
    {
843
0
      char* end = strrchr(ptr, '"');
844
0
      if (!end)
845
0
        goto fail;
846
0
      *end = '\0';
847
0
    }
848
0
  }
849
850
0
  *count = nArgs;
851
0
  success = TRUE;
852
0
fail:
853
0
  free(copy);
854
0
  if (!success)
855
0
  {
856
0
    if (count)
857
0
      *count = 0;
858
0
    free((void*)p);
859
0
    return nullptr;
860
0
  }
861
0
  return p;
862
0
}
863
864
char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
865
0
{
866
0
  return CommandLineParseCommaSeparatedValuesEx(nullptr, list, count);
867
0
}
868
869
char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
870
0
{
871
0
  return CommandLineToCommaSeparatedValuesEx(argc, argv, nullptr, 0);
872
0
}
873
874
static const char* filtered(const char* arg, const char* filters[], size_t number)
875
0
{
876
0
  if (number == 0)
877
0
    return arg;
878
0
  for (size_t x = 0; x < number; x++)
879
0
  {
880
0
    const char* filter = filters[x];
881
0
    size_t len = strlen(filter);
882
0
    if (_strnicmp(arg, filter, len) == 0)
883
0
      return &arg[len];
884
0
  }
885
0
  return nullptr;
886
0
}
887
888
char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
889
                                          size_t number)
890
0
{
891
0
  char* str = nullptr;
892
0
  size_t offset = 0;
893
0
  size_t size = WINPR_ASSERTING_INT_CAST(size_t, argc) + 1;
894
0
  if ((argc <= 0) || !argv)
895
0
    return nullptr;
896
897
0
  for (int x = 0; x < argc; x++)
898
0
    size += strlen(argv[x]);
899
900
0
  str = calloc(size, sizeof(char));
901
0
  if (!str)
902
0
    return nullptr;
903
0
  for (int x = 0; x < argc; x++)
904
0
  {
905
0
    int rc = 0;
906
0
    const char* arg = filtered(argv[x], filters, number);
907
0
    if (!arg)
908
0
      continue;
909
0
    rc = _snprintf(&str[offset], size - offset, "%s,", arg);
910
0
    if (rc <= 0)
911
0
    {
912
0
      free(str);
913
0
      return nullptr;
914
0
    }
915
0
    offset += (size_t)rc;
916
0
  }
917
0
  if (offset > 0)
918
0
    str[offset - 1] = '\0';
919
0
  return str;
920
0
}
921
922
void CommandLineParserFree(char** ptr)
923
0
{
924
0
  union
925
0
  {
926
0
    char* p;
927
0
    char** pp;
928
0
  } uptr;
929
0
  uptr.pp = ptr;
930
0
  free(uptr.p);
931
0
}