Coverage Report

Created: 2026-10-28 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htscmdline_selftest.c
Line
Count
Source
1
/* ------------------------------------------------------------ */
2
/*
3
HTTrack Website Copier, Offline Browser for Windows and Unix
4
Copyright (C) 1998 Xavier Roche and other contributors
5
6
SPDX-License-Identifier: GPL-3.0-or-later
7
8
This program is free software: you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation, either version 3 of the License, or
11
(at your option) any later version.
12
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
GNU General Public License for more details.
17
18
You should have received a copy of the GNU General Public License
19
along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21
Ethical use: we kindly ask that you NOT use this software to harvest email
22
addresses or to collect any other private information about people. Doing so
23
would dishonor our work and waste the many hours we have spent on it.
24
25
Please visit our Website: http://www.httrack.com
26
*/
27
28
/* ------------------------------------------------------------ */
29
/* File: htscmdline_selftest.c subroutines:                     */
30
/*       self-tests for the argument quoting grammar            */
31
/* Author: Xavier Roche                                         */
32
/* ------------------------------------------------------------ */
33
34
#include "htsselftest_int.h"
35
#include "htscmdline.h"
36
37
#include <limits.h>
38
#include <stdint.h>
39
40
/* ------------------------------------------------------------ */
41
/* Frozen copies of the code the shared helpers replaced. */
42
/* ------------------------------------------------------------ */
43
44
/* The old hts_split_cmdline(). */
45
0
static char **legacy_split_cmdline(char *cmd, int *nargs) {
46
0
  size_t nsep = 0;
47
0
  size_t capacity;
48
0
  size_t r;
49
0
  size_t w;
50
0
  int argc = 0;
51
0
  hts_boolean quoted = HTS_FALSE;
52
0
  char **argv;
53
54
0
  *nargs = 0;
55
0
  for (r = 0; cmd[r] != '\0'; r++) {
56
0
    if (cmd[r] == '\t' || cmd[r] == '\r' || cmd[r] == '\n') {
57
0
      cmd[r] = ' ';
58
0
    }
59
0
    if (cmd[r] == ' ') {
60
0
      nsep++;
61
0
    }
62
0
  }
63
0
  if (nsep > (size_t) INT_MAX - 1 || nsep > SIZE_MAX / sizeof(char *) - 2) {
64
0
    return NULL;
65
0
  }
66
0
  capacity = nsep + 2;
67
0
  argv = (char **) malloct(capacity * sizeof(char *));
68
0
  if (argv == NULL) {
69
0
    return NULL;
70
0
  }
71
0
  argv[argc++] = cmd;
72
0
  for (r = 0, w = 0; cmd[r] != '\0';) {
73
0
    if (quoted && cmd[r] == '\\' &&
74
0
        (cmd[r + 1] == '\\' || cmd[r + 1] == '\"')) {
75
0
      r++;
76
0
      cmd[w++] = cmd[r++];
77
0
    } else if (cmd[r] == '\"') {
78
0
      quoted = !quoted;
79
0
      cmd[w++] = cmd[r++];
80
0
    } else if (cmd[r] == ' ' && !quoted) {
81
0
      cmd[w++] = '\0';
82
0
      assertf((size_t) argc < capacity - 1);
83
0
      argv[argc++] = cmd + w;
84
0
      r++;
85
0
    } else {
86
0
      cmd[w++] = cmd[r++];
87
0
    }
88
0
  }
89
0
  cmd[w] = '\0';
90
0
  argv[argc] = NULL;
91
0
  *nargs = argc;
92
0
  return argv;
93
0
}
94
95
/* htscore.c's next_token(), the doit.log reader's scanner. */
96
0
static char *legacy_next_token(char *p, int flag) {
97
0
  int detect = 0;
98
0
  int quote = 0;
99
100
0
  p--;
101
0
  do {
102
0
    p++;
103
0
    if (flag && (*p == '\\')) {
104
0
      if (quote) {
105
0
        char c = '\0';
106
107
0
        if (*(p + 1) == '\\')
108
0
          c = '\\';
109
0
        else if (*(p + 1) == '"')
110
0
          c = '"';
111
0
        if (c) {
112
0
          *p = c;
113
0
          memmove(p + 1, p + 2, strlen(p + 2) + 1);
114
0
        }
115
0
      }
116
0
    } else if (*p == 34) {
117
0
      memmove(p, p + 1, strlen(p + 1) + 1);
118
0
      p--;
119
0
      quote = !quote;
120
0
    } else if (*p == 32) {
121
0
      if (!quote)
122
0
        detect = 1;
123
0
    } else if (*p == '\0') {
124
0
      p = NULL;
125
0
      detect = 1;
126
0
    }
127
0
  } while (!detect);
128
0
  return p;
129
0
}
130
131
/* htscoremain.c's doit.log reader loop, appending each token and a '\n'. */
132
0
static void legacy_doit_read(char *buff, String *out, int *ntok) {
133
0
  char *p = buff, *lastp;
134
135
0
  *ntok = 0;
136
0
  do {
137
0
    int quoted;
138
139
0
    lastp = p;
140
0
    quoted = (p != NULL && *p == '"');
141
0
    if (p) {
142
0
      p = legacy_next_token(p, 1);
143
0
      if (p) {
144
0
        *p = 0;
145
0
        p++;
146
0
      }
147
0
    }
148
0
    if (lastp) {
149
0
      if (strnotempty(lastp) || quoted) {
150
0
        StringCat(*out, lastp);
151
0
        StringAddchar(*out, '\n');
152
0
        (*ntok)++;
153
0
      }
154
0
    }
155
0
  } while (lastp != NULL);
156
0
}
157
158
/* htscoremain.c's doit.log writer, for one argument. */
159
0
static void legacy_doit_write(String *out, const char *arg) {
160
0
  if ((strchr(arg, ' ') != NULL) || (strchr(arg, '"') != NULL) ||
161
0
      (strchr(arg, '\\') != NULL)) {
162
0
    size_t j;
163
164
0
    StringAddchar(*out, '"');
165
0
    for (j = 0; arg[j] != '\0'; j++) {
166
0
      if (arg[j] == 34)
167
0
        StringCat(*out, "\\\"");
168
0
      else if (arg[j] == '\\')
169
0
        StringCat(*out, "\\\\");
170
0
      else
171
0
        StringAddchar(*out, arg[j]);
172
0
    }
173
0
    StringAddchar(*out, '"');
174
0
  } else if (strnotempty(arg) == 0) {
175
0
    StringCat(*out, "\"\"");
176
0
  } else {
177
0
    StringCat(*out, arg);
178
0
  }
179
0
}
180
181
/* htsserver.c's cat_cmdline_argn() without its HTML step, which runs after. */
182
0
static void legacy_cmdline_argn(String *out, const char *value, size_t len) {
183
0
  size_t i;
184
185
0
  for (i = 0; i < len; i++) {
186
0
    if (value[i] == '\\' || value[i] == '\"') {
187
0
      StringCat(*out, "\\");
188
0
    }
189
0
    StringMemcat(*out, &value[i], 1);
190
0
  }
191
0
}
192
193
/* htscoremain.c's argv quote strip, HTS_FALSE for "Missing quote". */
194
0
static hts_boolean legacy_unquote(char *arg) {
195
0
  if (arg[0] == '"') {
196
0
    char tempo[HTS_CDLMAXSIZE];
197
198
0
    strcpybuff(tempo, arg + 1);
199
0
    if (hts_lastchar(tempo) != '"') {
200
0
      return HTS_FALSE;
201
0
    }
202
0
    hts_choplastchar(tempo);
203
0
    strlcpybuff(arg, tempo, strlen(arg) + 1);
204
0
  }
205
0
  return HTS_TRUE;
206
0
}
207
208
/* ------------------------------------------------------------ */
209
/* Helpers */
210
/* ------------------------------------------------------------ */
211
212
/* Windows stdout is text mode, so control bytes are printed escaped. */
213
0
static void cat_shown(String *out, const char *s, size_t len) {
214
0
  size_t i;
215
216
0
  for (i = 0; i < len; i++) {
217
0
    const unsigned char c = (unsigned char) s[i];
218
219
0
    if (c == '\t') {
220
0
      StringCat(*out, "\\t");
221
0
    } else if (c == '\r') {
222
0
      StringCat(*out, "\\r");
223
0
    } else if (c == '\n') {
224
0
      StringCat(*out, "\\n");
225
0
    } else if (c < 0x20) {
226
0
      char hex[8];
227
228
0
      snprintf(hex, sizeof(hex), "\\x%02x", c);
229
0
      StringCat(*out, hex);
230
0
    } else {
231
0
      StringAddchar(*out, (char) c);
232
0
    }
233
0
  }
234
0
}
235
236
/* The vector as one string, one argument per '\n'-terminated line. */
237
0
static void cat_vector(String *out, char **argv, int argc) {
238
0
  int i;
239
240
0
  for (i = 0; i < argc; i++) {
241
0
    StringCat(*out, argv[i]);
242
0
    StringAddchar(*out, '\n');
243
0
  }
244
0
}
245
246
0
static uint32_t quote_rand(uint32_t *seed) {
247
0
  *seed = *seed * 1103515245u + 12345u;
248
0
  return *seed >> 16;
249
0
}
250
251
/* A random string over the bytes the grammar treats specially. */
252
0
static void quote_random(String *out, uint32_t *seed, size_t maxlen) {
253
0
  static const char alphabet[] = "ab \"\\\t\r\n,=<&'-\xe9";
254
0
  const size_t len = quote_rand(seed) % (maxlen + 1);
255
0
  size_t i;
256
257
0
  for (i = 0; i < len; i++) {
258
0
    StringAddchar(*out, alphabet[quote_rand(seed) % (sizeof(alphabet) - 1)]);
259
0
  }
260
0
}
261
262
typedef struct {
263
  unsigned long cases;
264
  unsigned long failures;
265
} quote_stats;
266
267
static void quote_fail(quote_stats *st, const char *site, const char *in,
268
0
                       const char *old, const char *new) {
269
0
  st->failures++;
270
0
  if (st->failures <= 20) {
271
0
    String msg = STRING_EMPTY;
272
273
0
    StringCat(msg, site);
274
0
    StringCat(msg, " differs on '");
275
0
    cat_shown(&msg, in, strlen(in));
276
0
    StringCat(msg, "': old '");
277
0
    cat_shown(&msg, old, strlen(old));
278
0
    StringCat(msg, "' new '");
279
0
    cat_shown(&msg, new, strlen(new));
280
0
    StringCat(msg, "'");
281
0
    printf("%s\n", StringBuff(msg));
282
0
    fflush(stdout); /* a later abort must not swallow it */
283
0
    StringFree(msg);
284
0
  }
285
0
}
286
287
/* Every old site against its replacement, on one input. */
288
0
static void quotediff_one(quote_stats *st, const char *in) {
289
0
  const size_t size = strlen(in) + 1;
290
0
  char *a = malloct(size), *b = malloct(size);
291
0
  String o = STRING_EMPTY, n = STRING_EMPTY;
292
0
  char **va, **vb;
293
0
  int na = 0, nb = 0;
294
295
0
  assertf(a != NULL && b != NULL);
296
0
  StringClear(o);
297
0
  StringClear(n);
298
299
  /* the WebHTTrack command line */
300
0
  memcpy(a, in, size);
301
0
  memcpy(b, in, size);
302
0
  va = legacy_split_cmdline(a, &na);
303
0
  vb = hts_split_cmdline(b, &nb);
304
0
  assertf(va != NULL && vb != NULL);
305
0
  cat_vector(&o, va, na);
306
0
  cat_vector(&n, vb, nb);
307
0
  st->cases++;
308
0
  if (na != nb || strcmp(StringBuff(o), StringBuff(n)) != 0)
309
0
    quote_fail(st, "hts_split_cmdline", in, StringBuff(o), StringBuff(n));
310
0
  freet(va);
311
0
  freet(vb);
312
0
  StringClear(o);
313
0
  StringClear(n);
314
315
  /* the doit.log line */
316
0
  memcpy(a, in, size);
317
0
  memcpy(b, in, size);
318
0
  legacy_doit_read(a, &o, &na);
319
0
  vb = hts_split_args(b, &nb, HTS_SPLIT_STRIP_QUOTES | HTS_SPLIT_DROP_EMPTY);
320
0
  assertf(vb != NULL);
321
0
  cat_vector(&n, vb, nb);
322
0
  st->cases++;
323
0
  if (na != nb || strcmp(StringBuff(o), StringBuff(n)) != 0)
324
0
    quote_fail(st, "doit.log reader", in, StringBuff(o), StringBuff(n));
325
0
  freet(vb);
326
0
  StringClear(o);
327
0
  StringClear(n);
328
329
  /* the doit.log writer */
330
0
  legacy_doit_write(&o, in);
331
0
  hts_quote_arg(&n, in);
332
0
  st->cases++;
333
0
  if (strcmp(StringBuff(o), StringBuff(n)) != 0)
334
0
    quote_fail(st, "doit.log writer", in, StringBuff(o), StringBuff(n));
335
0
  StringClear(o);
336
0
  StringClear(n);
337
338
  /* the WebHTTrack argument escape */
339
0
  legacy_cmdline_argn(&o, in, strlen(in));
340
0
  hts_escape_arg(&n, in, strlen(in));
341
0
  st->cases++;
342
0
  if (StringLength(o) != StringLength(n) ||
343
0
      memcmp(StringBuff(o), StringBuff(n), StringLength(o)) != 0)
344
0
    quote_fail(st, "argument escape", in, StringBuff(o), StringBuff(n));
345
346
  /* the argv quote strip */
347
0
  if (size <= HTS_CDLMAXSIZE) {
348
0
    hts_boolean ro, rn;
349
350
0
    memcpy(a, in, size);
351
0
    memcpy(b, in, size);
352
0
    ro = legacy_unquote(a);
353
0
    rn = hts_unquote_arg(b);
354
0
    st->cases++;
355
0
    if (ro != rn || (ro && strcmp(a, b) != 0))
356
0
      quote_fail(st, "argv unquote", in, ro ? a : "(missing quote)",
357
0
                 rn ? b : "(missing quote)");
358
0
  }
359
360
0
  StringFree(o);
361
0
  StringFree(n);
362
0
  freet(a);
363
0
  freet(b);
364
0
}
365
366
/* ------------------------------------------------------------ */
367
/* Self-tests */
368
/* ------------------------------------------------------------ */
369
370
/* Any difference is a bug. */
371
0
static int st_quotediff(httrackp *opt, int argc, char **argv) {
372
0
  static const char *const hand[] = {"",
373
0
                                     " ",
374
0
                                     "  ",
375
0
                                     "a",
376
0
                                     "a b",
377
0
                                     "a  b",
378
0
                                     " a",
379
0
                                     "a ",
380
0
                                     "\"\"",
381
0
                                     "\"\"\"\"",
382
0
                                     "a \"\" b",
383
0
                                     "\"a b\"",
384
0
                                     "a\"b c\"d",
385
0
                                     "\"a\\\"b\\\\c\"",
386
0
                                     "\"a\\",
387
0
                                     "a\\",
388
0
                                     "\\",
389
0
                                     "\\\"",
390
0
                                     "\"\\\"",
391
0
                                     "\"ab",
392
0
                                     "\"",
393
0
                                     "ab\"",
394
0
                                     "a\\ b",
395
0
                                     "\"a\\ b\"",
396
0
                                     "\"a\\nb\"",
397
0
                                     "a\tb",
398
0
                                     "a\rb\nc",
399
0
                                     "\"a\tb\"",
400
0
                                     "-F \"Mozilla 5.0\" -c8",
401
0
                                     "httrack -*\\** +*.png",
402
0
                                     "httrack \"C:\\\\dir\\\\sub\"",
403
0
                                     "-O \"/a b\" http://x/ \"\"",
404
0
                                     "\"(none)\"",
405
0
                                     "\"\"x\"\"",
406
0
                                     "a,=b <&'",
407
0
                                     "\xe9t\xe9",
408
0
                                     NULL};
409
0
  quote_stats st = {0, 0};
410
0
  uint32_t seed = 548;
411
0
  size_t i;
412
0
  int r;
413
414
0
  (void) opt;
415
0
  (void) argc;
416
0
  (void) argv;
417
0
  for (i = 0; hand[i] != NULL; i++)
418
0
    quotediff_one(&st, hand[i]);
419
  /* Longer than HTS_CDLMAXSIZE: every site runs it except the argv unquote. */
420
0
  {
421
0
    String big = STRING_EMPTY;
422
423
0
    StringAddchar(big, '"');
424
0
    for (i = 0; i < HTS_CDLMAXSIZE + 8; i++)
425
0
      StringAddchar(big, i % 7 == 0 ? ' ' : 'x');
426
0
    StringAddchar(big, '"');
427
0
    quotediff_one(&st, StringBuff(big));
428
0
    StringFree(big);
429
0
  }
430
0
  for (r = 0; r < 20000; r++) {
431
0
    String in = STRING_EMPTY;
432
433
0
    quote_random(&in, &seed, 24);
434
0
    quotediff_one(&st, StringLength(in) != 0 ? StringBuff(in) : "");
435
0
    StringFree(in);
436
0
  }
437
0
  printf("quotediff: %lu cases, %lu failures\n", st.cases, st.failures);
438
0
  if (st.failures != 0)
439
0
    return 1;
440
0
  printf("quotediff: OK\n");
441
0
  return 0;
442
0
}
443
444
/* split(quote(argv)) == argv, on both the doit.log and the WebHTTrack path. */
445
0
static int st_quoteprop(httrackp *opt, int argc, char **argv) {
446
0
  quote_stats st = {0, 0};
447
0
  uint32_t seed = 5480;
448
0
  int r;
449
450
0
  (void) opt;
451
0
  (void) argc;
452
0
  (void) argv;
453
0
  for (r = 0; r < 20000; r++) {
454
0
    const int n = (int) (quote_rand(&seed) % 6);
455
0
    String args[6];
456
0
    String want_doitlog = STRING_EMPTY, want_cmdline = STRING_EMPTY;
457
0
    String doitlog = STRING_EMPTY, cmdline = STRING_EMPTY, got = STRING_EMPTY;
458
0
    char *in;
459
0
    char **v;
460
0
    int i, nv = 0;
461
462
0
    StringClear(want_doitlog);
463
0
    StringClear(want_cmdline);
464
0
    StringClear(doitlog);
465
0
    StringClear(cmdline);
466
0
    StringClear(got);
467
468
    /* doit.log: hts_quote_arg() joined by spaces */
469
0
    for (i = 0; i < n; i++) {
470
0
      size_t k;
471
472
0
      StringInit(args[i]);
473
0
      StringClear(args[i]);
474
0
      quote_random(&args[i], &seed, 10);
475
0
      if (i != 0)
476
0
        StringAddchar(doitlog, ' ');
477
0
      hts_quote_arg(&doitlog, StringBuff(args[i]));
478
0
      StringCat(want_doitlog, StringBuff(args[i]));
479
0
      StringAddchar(want_doitlog, '\n');
480
0
      StringCat(cmdline, " \"");
481
0
      hts_escape_arg(&cmdline, StringBuff(args[i]), StringLength(args[i]));
482
0
      StringAddchar(cmdline, '\"');
483
      /* the command line folds TAB, CR and LF to a space, even quoted */
484
0
      for (k = 0; k < StringLength(args[i]); k++) {
485
0
        const char c = StringBuff(args[i])[k];
486
487
0
        StringAddchar(want_cmdline,
488
0
                      c == '\t' || c == '\r' || c == '\n' ? ' ' : c);
489
0
      }
490
0
      StringAddchar(want_cmdline, '\n');
491
0
    }
492
0
    in = strdupt(StringBuff(doitlog));
493
0
    assertf(in != NULL);
494
0
    v = hts_split_args(StringBuffRW(doitlog), &nv,
495
0
                       HTS_SPLIT_STRIP_QUOTES | HTS_SPLIT_DROP_EMPTY);
496
0
    assertf(v != NULL);
497
0
    cat_vector(&got, v, nv);
498
0
    freet(v);
499
0
    st.cases++;
500
0
    if (nv != n || strcmp(StringBuff(got), StringBuff(want_doitlog)) != 0)
501
0
      quote_fail(&st, "doit.log round trip", in, StringBuff(want_doitlog),
502
0
                 StringBuff(got));
503
0
    freet(in);
504
505
    /* WebHTTrack posts "prog" and each argument quoted, for htsweb.c */
506
0
    StringClear(got);
507
0
    {
508
0
      String cmd = STRING_EMPTY;
509
510
0
      StringCat(cmd, "prog");
511
0
      StringCat(cmd, StringBuff(cmdline));
512
0
      in = strdupt(StringBuff(cmd));
513
0
      assertf(in != NULL);
514
0
      v = hts_split_webcmd(StringBuffRW(cmd), &nv);
515
0
      assertf(v != NULL);
516
0
      for (i = 1; i < nv; i++) {
517
0
        StringCat(got, v[i]);
518
0
        StringAddchar(got, '\n');
519
0
      }
520
0
      freet(v);
521
0
      StringFree(cmd);
522
0
    }
523
0
    st.cases++;
524
0
    if (nv != n + 1 || strcmp(StringBuff(got), StringBuff(want_cmdline)) != 0)
525
0
      quote_fail(&st, "command line round trip", in, StringBuff(want_cmdline),
526
0
                 StringBuff(got));
527
0
    freet(in);
528
529
0
    for (i = 0; i < n; i++)
530
0
      StringFree(args[i]);
531
0
    StringFree(want_doitlog);
532
0
    StringFree(want_cmdline);
533
0
    StringFree(doitlog);
534
0
    StringFree(cmdline);
535
0
    StringFree(got);
536
0
  }
537
0
  printf("quoteprop: %lu cases, %lu failures\n", st.cases, st.failures);
538
0
  if (st.failures != 0)
539
0
    return 1;
540
0
  printf("quoteprop: OK\n");
541
0
  return 0;
542
0
}
543
544
/* Print hts_split_args(<line>) as [arg] items. FLAGS: f fold, s strip, d drop,
545
   or - for none. */
546
0
static int st_splitargs(httrackp *opt, int argc, char **argv) {
547
0
  String out = STRING_EMPTY;
548
0
  char *line;
549
0
  char **v;
550
0
  int flags = 0, nv = 0, i;
551
552
0
  (void) opt;
553
0
  if (argc != 2) {
554
0
    fprintf(stderr, "usage: -#test=splitargs <flags> <line>\n");
555
0
    return 1;
556
0
  }
557
0
  flags |= strchr(argv[0], 'f') != NULL ? HTS_SPLIT_FOLD_WS : 0;
558
0
  flags |= strchr(argv[0], 's') != NULL ? HTS_SPLIT_STRIP_QUOTES : 0;
559
0
  flags |= strchr(argv[0], 'd') != NULL ? HTS_SPLIT_DROP_EMPTY : 0;
560
0
  line = strdupt(argv[1]);
561
0
  assertf(line != NULL);
562
0
  v = hts_split_args(line, &nv, flags);
563
0
  assertf(v != NULL && v[nv] == NULL);
564
0
  for (i = 0; i < nv; i++) {
565
0
    StringAddchar(out, '[');
566
0
    cat_shown(&out, v[i], strlen(v[i]));
567
0
    StringAddchar(out, ']');
568
0
  }
569
0
  printf("%d:%s\n", nv, StringLength(out) != 0 ? StringBuff(out) : "");
570
0
  freet(v);
571
0
  freet(line);
572
0
  StringFree(out);
573
0
  return 0;
574
0
}
575
576
/* Print the arguments as hts_quote_arg() writes them, space-separated. */
577
0
static int st_quotearg(httrackp *opt, int argc, char **argv) {
578
0
  String line = STRING_EMPTY, out = STRING_EMPTY;
579
0
  int i;
580
581
0
  (void) opt;
582
0
  for (i = 0; i < argc; i++) {
583
0
    if (i != 0)
584
0
      StringAddchar(line, ' ');
585
0
    hts_quote_arg(&line, argv[i]);
586
0
  }
587
0
  cat_shown(&out, StringLength(line) != 0 ? StringBuff(line) : "",
588
0
            StringLength(line));
589
0
  printf("[%s]\n", StringLength(out) != 0 ? StringBuff(out) : "");
590
0
  StringFree(line);
591
0
  StringFree(out);
592
0
  return 0;
593
0
}
594
595
/* Print hts_unquote_arg(<arg>), or "missing quote". */
596
0
static int st_unquotearg(httrackp *opt, int argc, char **argv) {
597
0
  char *arg;
598
599
0
  (void) opt;
600
0
  if (argc != 1) {
601
0
    fprintf(stderr, "usage: -#test=unquotearg <arg>\n");
602
0
    return 1;
603
0
  }
604
0
  arg = strdupt(argv[0]);
605
0
  assertf(arg != NULL);
606
0
  if (hts_unquote_arg(arg))
607
0
    printf("[%s]\n", arg);
608
0
  else
609
0
    printf("missing quote\n");
610
0
  freet(arg);
611
0
  return 0;
612
0
}
613
614
0
static int st_ruleopt(httrackp *opt, int argc, char **argv) {
615
0
  (void) opt;
616
0
  if (argc != 1) {
617
0
    fprintf(stderr, "usage: -#test=ruleopt <arg>\n");
618
0
    return 1;
619
0
  }
620
0
  assertf(!hts_rule_would_be_read_as_opt(NULL));
621
  printf("%s\n", hts_rule_would_be_read_as_opt(argv[0]) ? "option" : "other");
622
0
  return 0;
623
0
}
624
625
/* ------------------------------------------------------------ */
626
/* Registry: this module's tests, in the order -#test lists them. */
627
/* ------------------------------------------------------------ */
628
629
const struct selftest_entry selftests_cmdline[] = {
630
    {"quotediff", "", "argument quoting against the code it replaced",
631
     st_quotediff},
632
    {"quoteprop", "", "split(quote(argv)) == argv over random argv",
633
     st_quoteprop},
634
    {"splitargs", "<flags> <line>", "split a line into arguments",
635
     st_splitargs},
636
    {"quotearg", "[arg...]", "quote arguments for a command line", st_quotearg},
637
    {"unquotearg", "<arg>", "strip an argument's quote pair", st_unquotearg},
638
    {"ruleopt", "<arg>", "is a '-' token an option or a scan rule", st_ruleopt},
639
    {NULL, NULL, NULL, NULL},
640
};