Imported Upstream version 3.3.3
[debian/amanda] / common-src / quoting-test.c
1 /*
2  * Copyright (c) 2008-2012 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 *
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
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
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
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
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
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
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
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
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
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 }