/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 | | }; |