1 /*
2 * Copyright (c) 2008-2013 Zmanda, Inc. All Rights Reserved.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 * Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
19 * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20 *
21 * Author: Dustin J. Mitchell <dustin@zmanda.com>
22 */
23
24 #include "amanda.h"
25 #include "testutils.h"
26 #include "util.h"
27
28 /* Utilities */
29
30 static char *
safestr(const char * str)31 safestr(const char *str) {
32 static char hex[] = "0123456789abcdef";
33 const char *p;
34 char *result = malloc(3 + strlen(str) * 3);
35 char *r = result;
36
37 *(r++) = '|';
38 for (p = str; *p; p++) {
39 if (isprint((int)*p)) {
40 *(r++) = *p;
41 } else {
42 *(r++) = '#';
43 *(r++) = hex[((*p)&0xf0) >> 4];
44 *(r++) = hex[(*p)&0xf];
45 }
46 }
47 *(r++) = '|';
48 *(r++) = '\0';
49
50 return result;
51 }
52
53 char * quotable_strings[] = {
54 "",
55 "simple",
56 "sp a ces",
57 "\"foo bar\"",
58 "back\\slash",
59 "escaped\\ space",
60 "escaped\\\"quote",
61 "balanced \"internal\" quotes",
62 "\"already quoted\" string",
63 "string that's \"already quoted\"",
64 "internal\"quote",
65 "bs-end\\",
66 "backslash\\nletter",
67 "backslash\\tletter",
68 "\t", "\r", "\n", "\f", "\004",
69 "new\nline",
70 "newline-end\n",
71 "ta\tb",
72 "tab-end\t",
73 "\\\\\\\\",
74 "\"",
75 NULL
76 };
77
78 /****
79 * Round-trip testing of quoting functions
80 */
81
82 static gboolean
test_round_trip(void)83 test_round_trip(void)
84 {
85 char **strp;
86 gboolean success = TRUE;
87
88 for (strp = quotable_strings; *strp; strp++) {
89 char *quoted, *unquoted;
90
91 quoted = quote_string(*strp);
92 unquoted = unquote_string(quoted);
93
94 /* if they're not the same, complain */
95 if (0 != strcmp(*strp, unquoted)) {
96 char *safe_orig = safestr(*strp);
97 char *safe_quoted = safestr(quoted);
98 char *safe_unquoted = safestr(unquoted);
99
100 printf(" bad round-trip: %s -quote_string-> %s -unquote_string-> %s\n",
101 safe_orig, safe_quoted, safe_unquoted);
102
103 amfree(safe_orig);
104 amfree(safe_quoted);
105 amfree(safe_unquoted);
106
107 success = FALSE;
108 }
109
110 amfree(quoted);
111 amfree(unquoted);
112 }
113
114 return success;
115 }
116
117 /***
118 * Test that the new split_quoted_strings acts identically to
119 * the old split(), albeit with a different set of arguments and
120 * return value. Note that we only test with a delimiter of " ",
121 * as split() is not used with any other delimiter.
122 */
123
124 static gboolean
compare_strv(const char ** exp,char ** got,const char * source,const char * original)125 compare_strv(
126 const char **exp,
127 char **got,
128 const char *source,
129 const char *original)
130 {
131 const char **a = exp;
132 char **b = got;
133 while (*a && *b) {
134 if (0 != strcmp(*a, *b))
135 break;
136 a++; b++;
137 }
138
139 /* did we exit the loop early, or were they different lengths? */
140 if (*a || *b) {
141 char *safe;
142
143 safe = safestr(original);
144 g_printf(" %s: expected [", safe);
145 amfree(safe);
146 for (a = exp; *a; a++) {
147 safe = safestr(*a);
148 g_printf("%s%s", safe, *(a+1)? ", " : "");
149 amfree(safe);
150 }
151 g_printf("] but got [");
152 for (b = got; *b; b++) {
153 safe = safestr(*b);
154 g_printf("%s%s", safe, *(b+1)? ", " : "");
155 amfree(safe);
156 }
157 g_printf("] using %s.\n", source);
158
159 return FALSE;
160 }
161
162 return TRUE;
163 }
164
165 static gboolean
test_split_quoted_strings(void)166 test_split_quoted_strings(void)
167 {
168 char **iter1, **iter2, **iter3;
169 gboolean success = TRUE;
170 char *middle_strings[] = {
171 "",
172 "foo",
173 "\"foo\"",
174 "sp aces",
175 NULL,
176 };
177
178 /* the idea here is to loop over all triples of strings, forming a
179 * string by quoting them with quote_string and inserting a space, then
180 * re-splitting with split_quoted_string. This should get us back to our
181 * starting point. */
182
183 for (iter1 = quotable_strings; *iter1; iter1++) {
184 for (iter2 = middle_strings; *iter2; iter2++) {
185 for (iter3 = quotable_strings; *iter3; iter3++) {
186 char *q1 = quote_string(*iter1);
187 char *q2 = quote_string(*iter2);
188 char *q3 = quote_string(*iter3);
189 const char *expected[4] = { *iter1, *iter2, *iter3, NULL };
190 char *combined = vstralloc(q1, " ", q2, " ", q3, NULL);
191 char **tokens;
192
193 tokens = split_quoted_strings(combined);
194
195 success = compare_strv(expected, tokens, "split_quoted_strings", combined)
196 && success;
197
198 amfree(q1);
199 amfree(q2);
200 amfree(q3);
201 amfree(combined);
202 g_strfreev(tokens);
203 }
204 }
205 }
206
207 return success;
208 }
209
210 /****
211 * Test splitting some edge cases and invalid strings
212 */
213
214 struct trial {
215 const char *combined;
216 const char *expected[5];
217 };
218
219 static gboolean
test_split_quoted_strings_edge(void)220 test_split_quoted_strings_edge(void)
221 {
222 gboolean success = TRUE;
223 struct trial trials[] = {
224 { "", { "", NULL, } },
225 { " ", { "", "", NULL } },
226 { " x", { "", "x", NULL } },
227 { "x ", { "x", "", NULL } },
228 { "x\\ y", { "x y", NULL } },
229 { "\\", { "", NULL } }, /* inv */
230 { "z\\", { "z", NULL } }, /* inv */
231 { "z\"", { "z", NULL } }, /* inv */
232 { "\" \" \"", { " ", "", NULL } }, /* inv */
233 { NULL, { NULL, } },
234 };
235 struct trial *trial = trials;
236
237 while (trial->combined) {
238 char **tokens = split_quoted_strings(trial->combined);
239
240 success = compare_strv(trial->expected, tokens,
241 "split_quoted_strings", trial->combined)
242 && success;
243
244 g_strfreev(tokens);
245 trial++;
246 }
247
248 return success;
249 }
250
251 /****
252 * Test unquoting of some pathological strings
253 */
254 static gboolean
test_unquote_string(void)255 test_unquote_string(void)
256 {
257 gboolean success = TRUE;
258 char *tests[] = {
259 "simple", "simple",
260 "\"quoted\"", "quoted",
261 "s p a c e", "s p a c e",
262
263 /* special escape characters */
264 "esc \\\" quote", "esc \" quote",
265 "esc \\t tab", "esc \t tab",
266 "esc \\\\ esc", "esc \\ esc",
267 "esc \\02 oct", "esc \02 oct",
268 "esc \\7 oct", "esc \7 oct",
269 "esc \\17 oct", "esc \17 oct",
270 "esc \\117 oct", "esc \117 oct",
271 "esc \\1117 oct", "esc \1117 oct", /* '7' is distinct char */
272
273 /* same, but pre-quoted */
274 "\"esc \\\" quote\"", "esc \" quote",
275 "\"esc \\t tab\"", "esc \t tab",
276 "\"esc \\\\ esc\"", "esc \\ esc",
277 "\"esc \\02 oct\"", "esc \02 oct",
278 "\"esc \\7 oct\"", "esc \7 oct",
279 "\"esc \\17 oct\"", "esc \17 oct",
280 "\"esc \\117 oct\"", "esc \117 oct",
281 "\"esc \\1117 oct\"", "esc \1117 oct", /* '7' is distinct char */
282
283 /* strips balanced quotes, even inside the string */
284 ">>\"x\"<<", ">>x<<",
285 ">>\"x\"-\"y\"<<", ">>x-y<<",
286
287 /* pathological, but valid */
288 "\\\\", "\\",
289 "\"\\\"\"", "\"",
290 "\"\\\\\"", "\\",
291 "--\\\"", "--\"",
292 "\\\"--", "\"--",
293
294 /* invalid strings (handling here is arbitrary, but these tests
295 * will alert us if the handling changes) */
296 "\\", "", /* trailing backslash is ignored */
297 "xx\\", "xx", /* ditto */
298 "\\\\\\\\\\\\\\", "\\\\\\", /* ditto */
299 "\\777", "\377", /* 0777 & 0xff = 0xff */
300 "\"--", "--", /* leading quote is dropped */
301 "--\"", "--", /* trailing quote is dropped */
302
303 NULL, NULL,
304 };
305 char **strp;
306
307 for (strp = tests; *strp;) {
308 char *quoted = *(strp++);
309 char *expected = *(strp++);
310 char *unquoted = unquote_string(quoted);
311
312 /* if they're not the same, complain */
313 if (0 != strcmp(expected, unquoted)) {
314 char *safe_quoted = safestr(quoted);
315 char *safe_unquoted = safestr(unquoted);
316 char *safe_expected = safestr(expected);
317
318 printf(" %s unquoted to %s; expected %s.\n",
319 safe_quoted, safe_unquoted, safe_expected);
320
321 amfree(safe_quoted);
322 amfree(safe_unquoted);
323 amfree(safe_expected);
324
325 success = FALSE;
326 }
327
328 amfree(unquoted);
329 }
330
331 return success;
332 }
333
334 /****
335 * Test the strquotedstr function
336 */
337 static gboolean
test_strquotedstr_skipping(void)338 test_strquotedstr_skipping(void)
339 {
340 char **iter1, **iter2;
341 gboolean success = TRUE;
342
343 /* the idea here is to loop over all pairs of strings, forming a
344 * string by quoting them with quote_string and inserting a space, then
345 * re-splitting with strquotedstr. This should get us back to our
346 * starting point. Note that we have to begin with a non-quoted identifier,
347 * becuse strquotedstr requires that strtok_r has already been called. */
348
349 for (iter1 = quotable_strings; *iter1; iter1++) {
350 for (iter2 = quotable_strings; *iter2; iter2++) {
351 char *q1 = quote_string(*iter1);
352 char *q2 = quote_string(*iter2);
353 char *combined = vstralloc("START ", q1, " ", q2, NULL);
354 char *copy = g_strdup(combined);
355 char *saveptr = NULL;
356 char *tok;
357 int i;
358
359 tok = strtok_r(copy, " ", &saveptr);
360
361 for (i = 1; i <= 2; i++) {
362 char *expected = (i == 1)? q1:q2;
363 tok = strquotedstr(&saveptr);
364 if (!tok) {
365 g_fprintf(stderr, "while parsing '%s', call %d to strquotedstr returned NULL\n",
366 combined, i);
367 success = FALSE;
368 goto next;
369 }
370 if (0 != strcmp(tok, expected)) {
371 char *safe = safestr(tok);
372
373 g_fprintf(stderr, "while parsing '%s', call %d to strquotedstr returned '%s' "
374 "but '%s' was expected.\n",
375 combined, i, safe, expected);
376 success = FALSE;
377 goto next;
378 }
379 }
380
381 if (strquotedstr(&saveptr) != NULL) {
382 g_fprintf(stderr, "while parsing '%s', call 3 to strquotedstr did not return NULL\n",
383 combined);
384 success = FALSE;
385 goto next;
386 }
387 next:
388 amfree(q1);
389 amfree(q2);
390 amfree(copy);
391 amfree(combined);
392 }
393 }
394
395 return success;
396 }
397
398 static gboolean
test_strquotedstr_edge_invalid(void)399 test_strquotedstr_edge_invalid(void)
400 {
401 gboolean success = TRUE;
402 char *invalid[] = {
403 "X \"abc", /* unterminated */
404 "X \"ab cd", /* unterminated second token */
405 "X a\"b cd", /* unterminated second token with internal quote */
406 "X b\\", /* trailing backslash */
407 "X \"b\\", /* trailing backslash in quote */
408 "X \"b\\\"", /* backslash'd ending quote */
409 NULL
410 };
411 char **iter;
412
413 /* run strquotedstr on a bunch of invalid tokens. It should return NULL */
414
415 for (iter = invalid; *iter; iter++) {
416 char *copy = g_strdup(*iter);
417 char *tok;
418 char *saveptr = NULL;
419
420 tok = strtok_r(copy, " ", &saveptr);
421 tok = strquotedstr(&saveptr);
422 if (tok != NULL) {
423 g_fprintf(stderr, "while parsing invalid '%s', strquotedstr did not return NULL\n",
424 *iter);
425 success = FALSE;
426 }
427
428 amfree(copy);
429 }
430
431 return success;
432 }
433
434 static gboolean
test_strquotedstr_edge_valid(void)435 test_strquotedstr_edge_valid(void)
436 {
437 gboolean success = TRUE;
438 char *valid[] = {
439 /* input */ /* expected (omitting "X") */
440 "X abc\\ def", "abc\\ def", /* backslashed space */
441 "X \"abc\\ def\"", "\"abc\\ def\"", /* quoted, backslashed space */
442 "X a\" \"b", "a\" \"b", /* quoted spaces */
443 NULL, NULL
444 };
445 char **iter;
446
447 /* run strquotedstr on a bunch of valid, but tricky, tokens. It should return NULL */
448
449 for (iter = valid; *iter; iter += 2) {
450 char *copy = g_strdup(*iter);
451 char *expected = *(iter+1);
452 char *tok;
453 char *saveptr = NULL;
454
455 tok = strtok_r(copy, " ", &saveptr);
456 tok = strquotedstr(&saveptr);
457 if (tok == NULL) {
458 g_fprintf(stderr, "while parsing valid '%s', strquotedstr returned NULL\n",
459 *iter);
460 success = FALSE;
461 } else if (0 != strcmp(tok, expected)) {
462 g_fprintf(stderr, "while parsing valid '%s', strquotedstr returned '%s' while "
463 "'%s' was expected\n",
464 *iter, tok, expected);
465 success = FALSE;
466 }
467
468 amfree(copy);
469 }
470
471 return success;
472 }
473
474 /*
475 * Main driver
476 */
477
478 int
main(int argc,char ** argv)479 main(int argc, char **argv)
480 {
481 static TestUtilsTest tests[] = {
482 TU_TEST(test_round_trip, 90),
483 TU_TEST(test_unquote_string, 90),
484 TU_TEST(test_split_quoted_strings, 90),
485 TU_TEST(test_split_quoted_strings_edge, 90),
486 TU_TEST(test_strquotedstr_skipping, 90),
487 TU_TEST(test_strquotedstr_edge_invalid, 90),
488 TU_TEST(test_strquotedstr_edge_valid, 90),
489 TU_END()
490 };
491
492 return testutils_run_tests(argc, argv, tests);
493 }
494