Coverage Report

Created: 2025-07-11 06:46

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